1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright 2021 Advanced Micro Devices, Inc.
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
19  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21  * OTHER DEALINGS IN THE SOFTWARE.
22  *
23  * Authors: AMD
24  *
25  */
26 
27 #include "dc.h"
28 #include "dc_link_dpia.h"
29 #include "inc/core_status.h"
30 #include "dc_link.h"
31 #include "dc_link_dp.h"
32 #include "dpcd_defs.h"
33 #include "link_hwss.h"
34 #include "dm_helpers.h"
35 #include "dmub/inc/dmub_cmd.h"
36 #include "inc/link_dpcd.h"
37 
38 #define DC_LOGGER \
39 	link->ctx->logger
40 
dpcd_get_tunneling_device_data(struct dc_link * link)41 enum dc_status dpcd_get_tunneling_device_data(struct dc_link *link)
42 {
43 	enum dc_status status = DC_OK;
44 	uint8_t dpcd_dp_tun_data[3] = {0};
45 	uint8_t dpcd_topology_data[DPCD_USB4_TOPOLOGY_ID_LEN] = {0};
46 	uint8_t i = 0;
47 
48 	status = core_link_read_dpcd(link,
49 			DP_TUNNELING_CAPABILITIES_SUPPORT,
50 			dpcd_dp_tun_data,
51 			sizeof(dpcd_dp_tun_data));
52 
53 	status = core_link_read_dpcd(link,
54 			DP_USB4_ROUTER_TOPOLOGY_ID,
55 			dpcd_topology_data,
56 			sizeof(dpcd_topology_data));
57 
58 	link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.raw =
59 			dpcd_dp_tun_data[DP_TUNNELING_CAPABILITIES_SUPPORT -
60 					 DP_TUNNELING_CAPABILITIES_SUPPORT];
61 	link->dpcd_caps.usb4_dp_tun_info.dpia_info.raw =
62 			dpcd_dp_tun_data[DP_IN_ADAPTER_INFO - DP_TUNNELING_CAPABILITIES_SUPPORT];
63 	link->dpcd_caps.usb4_dp_tun_info.usb4_driver_id =
64 			dpcd_dp_tun_data[DP_USB4_DRIVER_ID - DP_TUNNELING_CAPABILITIES_SUPPORT];
65 
66 	for (i = 0; i < DPCD_USB4_TOPOLOGY_ID_LEN; i++)
67 		link->dpcd_caps.usb4_dp_tun_info.usb4_topology_id[i] = dpcd_topology_data[i];
68 
69 	return status;
70 }
71 
72 /* Configure link as prescribed in link_setting; set LTTPR mode; and
73  * Initialize link training settings.
74  * Abort link training if sink unplug detected.
75  *
76  * @param link DPIA link being trained.
77  * @param[in] link_setting Lane count, link rate and downspread control.
78  * @param[out] lt_settings Link settings and drive settings (voltage swing and pre-emphasis).
79  */
dpia_configure_link(struct dc_link * link,const struct dc_link_settings * link_setting,struct link_training_settings * lt_settings)80 static enum link_training_result dpia_configure_link(struct dc_link *link,
81 		const struct dc_link_settings *link_setting,
82 		struct link_training_settings *lt_settings)
83 {
84 	enum dc_status status;
85 	bool fec_enable;
86 
87 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) configuring\n - LTTPR mode(%d)\n",
88 				__func__,
89 				link->link_id.enum_id - ENUM_ID_1,
90 				link->lttpr_mode);
91 
92 	dp_decide_training_settings(link,
93 		link_setting,
94 		lt_settings);
95 
96 	status = dpcd_configure_channel_coding(link, lt_settings);
97 	if (status != DC_OK && !link->hpd_status)
98 		return LINK_TRAINING_ABORT;
99 
100 	/* Configure lttpr mode */
101 	status = dpcd_configure_lttpr_mode(link, lt_settings);
102 	if (status != DC_OK && !link->hpd_status)
103 		return LINK_TRAINING_ABORT;
104 
105 	/* Set link rate, lane count and spread. */
106 	status = dpcd_set_link_settings(link, lt_settings);
107 	if (status != DC_OK && !link->hpd_status)
108 		return LINK_TRAINING_ABORT;
109 
110 	if (link->preferred_training_settings.fec_enable)
111 		fec_enable = *link->preferred_training_settings.fec_enable;
112 	else
113 		fec_enable = true;
114 	status = dp_set_fec_ready(link, fec_enable);
115 	if (status != DC_OK && !link->hpd_status)
116 		return LINK_TRAINING_ABORT;
117 
118 	return LINK_TRAINING_SUCCESS;
119 }
120 
core_link_send_set_config(struct dc_link * link,uint8_t msg_type,uint8_t msg_data)121 static enum dc_status core_link_send_set_config(struct dc_link *link,
122 	uint8_t msg_type,
123 	uint8_t msg_data)
124 {
125 	struct set_config_cmd_payload payload;
126 	enum set_config_status set_config_result = SET_CONFIG_PENDING;
127 
128 	/* prepare set_config payload */
129 	payload.msg_type = msg_type;
130 	payload.msg_data = msg_data;
131 
132 	if (!link->ddc->ddc_pin && !link->aux_access_disabled &&
133 	    (dm_helpers_dmub_set_config_sync(link->ctx, link,
134 					     &payload, &set_config_result) == -1)) {
135 		return DC_ERROR_UNEXPECTED;
136 	}
137 
138 	/* set_config should return ACK if successful */
139 	return (set_config_result == SET_CONFIG_ACK_RECEIVED) ? DC_OK : DC_ERROR_UNEXPECTED;
140 }
141 
142 /* Build SET_CONFIG message data payload for specified message type. */
dpia_build_set_config_data(enum dpia_set_config_type type,struct dc_link * link,struct link_training_settings * lt_settings)143 static uint8_t dpia_build_set_config_data(enum dpia_set_config_type type,
144 		struct dc_link *link,
145 		struct link_training_settings *lt_settings)
146 {
147 	union dpia_set_config_data data;
148 
149 	data.raw = 0;
150 
151 	switch (type) {
152 	case DPIA_SET_CFG_SET_LINK:
153 		data.set_link.mode = link->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT ? 1 : 0;
154 		break;
155 	case DPIA_SET_CFG_SET_PHY_TEST_MODE:
156 		break;
157 	case DPIA_SET_CFG_SET_VSPE:
158 		/* Assume all lanes have same drive settings. */
159 		data.set_vspe.swing = lt_settings->lane_settings[0].VOLTAGE_SWING;
160 		data.set_vspe.pre_emph = lt_settings->lane_settings[0].PRE_EMPHASIS;
161 		data.set_vspe.max_swing_reached =
162 			lt_settings->lane_settings[0].VOLTAGE_SWING ==
163 			VOLTAGE_SWING_MAX_LEVEL ? 1 : 0;
164 		data.set_vspe.max_pre_emph_reached =
165 			lt_settings->lane_settings[0].PRE_EMPHASIS ==
166 			PRE_EMPHASIS_MAX_LEVEL ? 1 : 0;
167 		break;
168 	default:
169 		ASSERT(false); /* Message type not supported by helper function. */
170 		break;
171 	}
172 
173 	return data.raw;
174 }
175 
176 /* Convert DC training pattern to DPIA training stage. */
convert_trng_ptn_to_trng_stg(enum dc_dp_training_pattern tps)177 static enum dpia_set_config_ts convert_trng_ptn_to_trng_stg(enum dc_dp_training_pattern tps)
178 {
179 	enum dpia_set_config_ts ts;
180 
181 	switch (tps) {
182 	case DP_TRAINING_PATTERN_SEQUENCE_1:
183 		ts = DPIA_TS_TPS1;
184 		break;
185 	case DP_TRAINING_PATTERN_SEQUENCE_2:
186 		ts = DPIA_TS_TPS2;
187 		break;
188 	case DP_TRAINING_PATTERN_SEQUENCE_3:
189 		ts = DPIA_TS_TPS3;
190 		break;
191 	case DP_TRAINING_PATTERN_SEQUENCE_4:
192 		ts = DPIA_TS_TPS4;
193 		break;
194 	default:
195 		ts = DPIA_TS_DPRX_DONE;
196 		ASSERT(false); /* TPS not supported by helper function. */
197 		break;
198 	}
199 
200 	return ts;
201 }
202 
203 /* Write training pattern to DPCD. */
dpcd_set_lt_pattern(struct dc_link * link,enum dc_dp_training_pattern pattern,uint32_t hop)204 static enum dc_status dpcd_set_lt_pattern(struct dc_link *link,
205 	enum dc_dp_training_pattern pattern,
206 	uint32_t hop)
207 {
208 	union dpcd_training_pattern dpcd_pattern = { {0} };
209 	uint32_t dpcd_tps_offset = DP_TRAINING_PATTERN_SET;
210 	enum dc_status status;
211 
212 	if (hop != DPRX)
213 		dpcd_tps_offset = DP_TRAINING_PATTERN_SET_PHY_REPEATER1 +
214 			((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (hop - 1));
215 
216 	/* DpcdAddress_TrainingPatternSet */
217 	dpcd_pattern.v1_4.TRAINING_PATTERN_SET =
218 		dc_dp_training_pattern_to_dpcd_training_pattern(link, pattern);
219 
220 	dpcd_pattern.v1_4.SCRAMBLING_DISABLE =
221 		dc_dp_initialize_scrambling_data_symbols(link, pattern);
222 
223 	if (hop != DPRX) {
224 		DC_LOG_HW_LINK_TRAINING("%s\n LTTPR Repeater ID: %d\n 0x%X pattern = %x\n",
225 					__func__,
226 					hop,
227 					dpcd_tps_offset,
228 					dpcd_pattern.v1_4.TRAINING_PATTERN_SET);
229 	} else {
230 		DC_LOG_HW_LINK_TRAINING("%s\n 0x%X pattern = %x\n",
231 					__func__,
232 					dpcd_tps_offset,
233 					dpcd_pattern.v1_4.TRAINING_PATTERN_SET);
234 	}
235 
236 	status = core_link_write_dpcd(link,
237 				      dpcd_tps_offset,
238 				      &dpcd_pattern.raw,
239 				      sizeof(dpcd_pattern.raw));
240 
241 	return status;
242 }
243 
244 /* Execute clock recovery phase of link training for specified hop in display
245  * path.in non-transparent mode:
246  * - Driver issues both DPCD and SET_CONFIG transactions.
247  * - TPS1 is transmitted for any hops downstream of DPOA.
248  * - Drive (VS/PE) only transmitted for the hop immediately downstream of DPOA.
249  * - CR for the first hop (DPTX-to-DPIA) is assumed to be successful.
250  *
251  * @param link DPIA link being trained.
252  * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
253  * @param hop The Hop in display path. DPRX = 0.
254  */
dpia_training_cr_non_transparent(struct dc_link * link,struct link_training_settings * lt_settings,uint32_t hop)255 static enum link_training_result dpia_training_cr_non_transparent(struct dc_link *link,
256 		struct link_training_settings *lt_settings,
257 		uint32_t hop)
258 {
259 	enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0;
260 	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
261 	enum dc_status status;
262 	uint32_t retries_cr = 0; /* Number of consecutive attempts with same VS or PE. */
263 	uint32_t retry_count = 0;
264 	/* From DP spec, CR read interval is always 100us. */
265 	uint32_t wait_time_microsec = TRAINING_AUX_RD_INTERVAL;
266 	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
267 	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = { { {0} } };
268 	union lane_align_status_updated dpcd_lane_status_updated = { {0} };
269 	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = { { {0} } };
270 	uint8_t set_cfg_data;
271 	enum dpia_set_config_ts ts;
272 
273 	repeater_cnt = dp_convert_to_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
274 
275 	/* Cap of LINK_TRAINING_MAX_CR_RETRY attempts at clock recovery.
276 	 * Fix inherited from perform_clock_recovery_sequence() -
277 	 * the DP equivalent of this function:
278 	 * Required for Synaptics MST hub which can put the LT in
279 	 * infinite loop by switching the VS between level 0 and level 1
280 	 * continuously.
281 	 */
282 	while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
283 	       (retry_count < LINK_TRAINING_MAX_CR_RETRY)) {
284 		/* DPTX-to-DPIA */
285 		if (hop == repeater_cnt) {
286 			/* Send SET_CONFIG(SET_LINK:LC,LR,LTTPR) to notify DPOA that
287 			 * non-transparent link training has started.
288 			 * This also enables the transmission of clk_sync packets.
289 			 */
290 			set_cfg_data = dpia_build_set_config_data(DPIA_SET_CFG_SET_LINK,
291 					link,
292 					lt_settings);
293 			status = core_link_send_set_config(link,
294 					DPIA_SET_CFG_SET_LINK,
295 					set_cfg_data);
296 			/* CR for this hop is considered successful as long as
297 			 * SET_CONFIG message is acknowledged by DPOA.
298 			 */
299 			if (status == DC_OK)
300 				result = LINK_TRAINING_SUCCESS;
301 			else
302 				result = LINK_TRAINING_ABORT;
303 			break;
304 		}
305 
306 		/* DPOA-to-x */
307 		/* Instruct DPOA to transmit TPS1 then update DPCD. */
308 		if (retry_count == 0) {
309 			ts = convert_trng_ptn_to_trng_stg(lt_settings->pattern_for_cr);
310 			status = core_link_send_set_config(link,
311 					DPIA_SET_CFG_SET_TRAINING,
312 					ts);
313 			if (status != DC_OK) {
314 				result = LINK_TRAINING_ABORT;
315 				break;
316 			}
317 			status = dpcd_set_lt_pattern(link, lt_settings->pattern_for_cr, hop);
318 			if (status != DC_OK) {
319 				result = LINK_TRAINING_ABORT;
320 				break;
321 			}
322 		}
323 
324 		/* Update DPOA drive settings then DPCD. DPOA does only adjusts
325 		 * drive settings for hops immediately downstream.
326 		 */
327 		if (hop == repeater_cnt - 1) {
328 			set_cfg_data = dpia_build_set_config_data(DPIA_SET_CFG_SET_VSPE,
329 					link,
330 					lt_settings);
331 			status = core_link_send_set_config(link,
332 					DPIA_SET_CFG_SET_VSPE,
333 					set_cfg_data);
334 			if (status != DC_OK) {
335 				result = LINK_TRAINING_ABORT;
336 				break;
337 			}
338 		}
339 		status = dpcd_set_lane_settings(link, lt_settings, hop);
340 		if (status != DC_OK) {
341 			result = LINK_TRAINING_ABORT;
342 			break;
343 		}
344 
345 		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
346 
347 		/* Read status and adjustment requests from DPCD. */
348 		status = dp_get_lane_status_and_lane_adjust(
349 				link,
350 				lt_settings,
351 				dpcd_lane_status,
352 				&dpcd_lane_status_updated,
353 				dpcd_lane_adjust,
354 				hop);
355 		if (status != DC_OK) {
356 			result = LINK_TRAINING_ABORT;
357 			break;
358 		}
359 
360 		/* Check if clock recovery successful. */
361 		if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
362 			result = LINK_TRAINING_SUCCESS;
363 			break;
364 		}
365 
366 		result = dp_get_cr_failure(lane_count, dpcd_lane_status);
367 
368 		if (dp_is_max_vs_reached(lt_settings))
369 			break;
370 
371 		/* Count number of attempts with same drive settings.
372 		 * Note: settings are the same for all lanes,
373 		 * so comparing first lane is sufficient.
374 		 */
375 		if ((lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
376 				dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
377 				&& (lt_settings->dpcd_lane_settings[0].bits.PRE_EMPHASIS_SET ==
378 						dpcd_lane_adjust[0].bits.PRE_EMPHASIS_LANE))
379 			retries_cr++;
380 		else
381 			retries_cr = 0;
382 
383 		/* Update VS/PE. */
384 		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
385 				lt_settings->lane_settings,
386 				lt_settings->dpcd_lane_settings);
387 		retry_count++;
388 	}
389 
390 	/* Abort link training if clock recovery failed due to HPD unplug. */
391 	if (!link->hpd_status)
392 		result = LINK_TRAINING_ABORT;
393 
394 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) clock recovery\n"
395 		" -hop(%d)\n - result(%d)\n - retries(%d)\n",
396 		__func__,
397 		link->link_id.enum_id - ENUM_ID_1,
398 		hop,
399 		result,
400 		retry_count);
401 
402 	return result;
403 }
404 
405 /* Execute clock recovery phase of link training in transparent LTTPR mode:
406  * - Driver only issues DPCD transactions and leaves USB4 tunneling (SET_CONFIG) messages to DPIA.
407  * - Driver writes TPS1 to DPCD to kick off training.
408  * - Clock recovery (CR) for link is handled by DPOA, which reports result to DPIA on completion.
409  * - DPIA communicates result to driver by updating CR status when driver reads DPCD.
410  *
411  * @param link DPIA link being trained.
412  * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
413  */
dpia_training_cr_transparent(struct dc_link * link,struct link_training_settings * lt_settings)414 static enum link_training_result dpia_training_cr_transparent(struct dc_link *link,
415 		struct link_training_settings *lt_settings)
416 {
417 	enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0;
418 	enum dc_status status;
419 	uint32_t retries_cr = 0; /* Number of consecutive attempts with same VS or PE. */
420 	uint32_t retry_count = 0;
421 	uint32_t wait_time_microsec = lt_settings->cr_pattern_time;
422 	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
423 	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = { { {0} } };
424 	union lane_align_status_updated dpcd_lane_status_updated = { {0} };
425 	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = { { {0} } };
426 
427 	/* Cap of LINK_TRAINING_MAX_CR_RETRY attempts at clock recovery.
428 	 * Fix inherited from perform_clock_recovery_sequence() -
429 	 * the DP equivalent of this function:
430 	 * Required for Synaptics MST hub which can put the LT in
431 	 * infinite loop by switching the VS between level 0 and level 1
432 	 * continuously.
433 	 */
434 	while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
435 	       (retry_count < LINK_TRAINING_MAX_CR_RETRY)) {
436 		/* Write TPS1 (not VS or PE) to DPCD to start CR phase.
437 		 * DPIA sends SET_CONFIG(SET_LINK) to notify DPOA to
438 		 * start link training.
439 		 */
440 		if (retry_count == 0) {
441 			status = dpcd_set_lt_pattern(link, lt_settings->pattern_for_cr, DPRX);
442 			if (status != DC_OK) {
443 				result = LINK_TRAINING_ABORT;
444 				break;
445 			}
446 		}
447 
448 		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
449 
450 		/* Read status and adjustment requests from DPCD. */
451 		status = dp_get_lane_status_and_lane_adjust(
452 				link,
453 				lt_settings,
454 				dpcd_lane_status,
455 				&dpcd_lane_status_updated,
456 				dpcd_lane_adjust,
457 				DPRX);
458 		if (status != DC_OK) {
459 			result = LINK_TRAINING_ABORT;
460 			break;
461 		}
462 
463 		/* Check if clock recovery successful. */
464 		if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
465 			result = LINK_TRAINING_SUCCESS;
466 			break;
467 		}
468 
469 		result = dp_get_cr_failure(lane_count, dpcd_lane_status);
470 
471 		if (dp_is_max_vs_reached(lt_settings))
472 			break;
473 
474 		/* Count number of attempts with same drive settings.
475 		 * Note: settings are the same for all lanes,
476 		 * so comparing first lane is sufficient.
477 		 */
478 		if ((lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
479 				dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
480 				&& (lt_settings->dpcd_lane_settings[0].bits.PRE_EMPHASIS_SET ==
481 						dpcd_lane_adjust[0].bits.PRE_EMPHASIS_LANE))
482 			retries_cr++;
483 		else
484 			retries_cr = 0;
485 
486 		/* Update VS/PE. */
487 		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
488 				lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
489 		retry_count++;
490 	}
491 
492 	/* Abort link training if clock recovery failed due to HPD unplug. */
493 	if (!link->hpd_status)
494 		result = LINK_TRAINING_ABORT;
495 
496 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) clock recovery\n"
497 		" -hop(%d)\n - result(%d)\n - retries(%d)\n",
498 		__func__,
499 		link->link_id.enum_id - ENUM_ID_1,
500 		DPRX,
501 		result,
502 		retry_count);
503 
504 	return result;
505 }
506 
507 /* Execute clock recovery phase of link training for specified hop in display
508  * path.
509  *
510  * @param link DPIA link being trained.
511  * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
512  * @param hop The Hop in display path. DPRX = 0.
513  */
dpia_training_cr_phase(struct dc_link * link,struct link_training_settings * lt_settings,uint32_t hop)514 static enum link_training_result dpia_training_cr_phase(struct dc_link *link,
515 		struct link_training_settings *lt_settings,
516 		uint32_t hop)
517 {
518 	enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0;
519 
520 	if (link->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT)
521 		result = dpia_training_cr_non_transparent(link, lt_settings, hop);
522 	else
523 		result = dpia_training_cr_transparent(link, lt_settings);
524 
525 	return result;
526 }
527 
528 /* Return status read interval during equalization phase. */
dpia_get_eq_aux_rd_interval(const struct dc_link * link,const struct link_training_settings * lt_settings,uint32_t hop)529 static uint32_t dpia_get_eq_aux_rd_interval(const struct dc_link *link,
530 		const struct link_training_settings *lt_settings,
531 		uint32_t hop)
532 {
533 	uint32_t wait_time_microsec;
534 
535 	if (hop == DPRX)
536 		wait_time_microsec = lt_settings->eq_pattern_time;
537 	else
538 		wait_time_microsec =
539 				dp_translate_training_aux_read_interval(
540 					link->dpcd_caps.lttpr_caps.aux_rd_interval[hop - 1]);
541 
542 #if defined(CONFIG_DRM_AMD_DC_DCN)
543 	/* Check debug option for extending aux read interval. */
544 	if (link->dc->debug.dpia_debug.bits.extend_aux_rd_interval)
545 		wait_time_microsec = DPIA_DEBUG_EXTENDED_AUX_RD_INTERVAL_US;
546 #endif
547 
548 	return wait_time_microsec;
549 }
550 
551 /* Execute equalization phase of link training for specified hop in display
552  * path in non-transparent mode:
553  * - driver issues both DPCD and SET_CONFIG transactions.
554  * - TPSx is transmitted for any hops downstream of DPOA.
555  * - Drive (VS/PE) only transmitted for the hop immediately downstream of DPOA.
556  * - EQ for the first hop (DPTX-to-DPIA) is assumed to be successful.
557  * - DPRX EQ only reported successful when both DPRX and DPIA requirements
558  * (clk sync packets sent) fulfilled.
559  *
560  * @param link DPIA link being trained.
561  * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
562  * @param hop The Hop in display path. DPRX = 0.
563  */
dpia_training_eq_non_transparent(struct dc_link * link,struct link_training_settings * lt_settings,uint32_t hop)564 static enum link_training_result dpia_training_eq_non_transparent(struct dc_link *link,
565 		struct link_training_settings *lt_settings,
566 		uint32_t hop)
567 {
568 	enum link_training_result result = LINK_TRAINING_EQ_FAIL_EQ;
569 	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
570 	uint32_t retries_eq = 0;
571 	enum dc_status status;
572 	enum dc_dp_training_pattern tr_pattern;
573 	uint32_t wait_time_microsec;
574 	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
575 	union lane_align_status_updated dpcd_lane_status_updated = { {0} };
576 	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = { { {0} } };
577 	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = { { {0} } };
578 	uint8_t set_cfg_data;
579 	enum dpia_set_config_ts ts;
580 
581 	/* Training pattern is TPS4 for repeater;
582 	 * TPS2/3/4 for DPRX depending on what it supports.
583 	 */
584 	if (hop == DPRX)
585 		tr_pattern = lt_settings->pattern_for_eq;
586 	else
587 		tr_pattern = DP_TRAINING_PATTERN_SEQUENCE_4;
588 
589 	repeater_cnt = dp_convert_to_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
590 
591 	for (retries_eq = 0; retries_eq < LINK_TRAINING_MAX_RETRY_COUNT; retries_eq++) {
592 		/* DPTX-to-DPIA equalization always successful. */
593 		if (hop == repeater_cnt) {
594 			result = LINK_TRAINING_SUCCESS;
595 			break;
596 		}
597 
598 		/* Instruct DPOA to transmit TPSn then update DPCD. */
599 		if (retries_eq == 0) {
600 			ts = convert_trng_ptn_to_trng_stg(tr_pattern);
601 			status = core_link_send_set_config(link,
602 					DPIA_SET_CFG_SET_TRAINING,
603 					ts);
604 			if (status != DC_OK) {
605 				result = LINK_TRAINING_ABORT;
606 				break;
607 			}
608 			status = dpcd_set_lt_pattern(link, tr_pattern, hop);
609 			if (status != DC_OK) {
610 				result = LINK_TRAINING_ABORT;
611 				break;
612 			}
613 		}
614 
615 		/* Update DPOA drive settings then DPCD. DPOA only adjusts
616 		 * drive settings for hop immediately downstream.
617 		 */
618 		if (hop == repeater_cnt - 1) {
619 			set_cfg_data = dpia_build_set_config_data(DPIA_SET_CFG_SET_VSPE,
620 								  link,
621 								  lt_settings);
622 			status = core_link_send_set_config(link,
623 							   DPIA_SET_CFG_SET_VSPE,
624 							   set_cfg_data);
625 			if (status != DC_OK) {
626 				result = LINK_TRAINING_ABORT;
627 				break;
628 			}
629 		}
630 		status = dpcd_set_lane_settings(link, lt_settings, hop);
631 		if (status != DC_OK) {
632 			result = LINK_TRAINING_ABORT;
633 			break;
634 		}
635 
636 		/* Extend wait time on second equalisation attempt on final hop to
637 		 * ensure clock sync packets have been sent.
638 		 */
639 		if (hop == DPRX && retries_eq == 1)
640 			wait_time_microsec = max(wait_time_microsec, (uint32_t)DPIA_CLK_SYNC_DELAY);
641 		else
642 			wait_time_microsec = dpia_get_eq_aux_rd_interval(link, lt_settings, hop);
643 
644 		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
645 
646 		/* Read status and adjustment requests from DPCD. */
647 		status = dp_get_lane_status_and_lane_adjust(
648 				link,
649 				lt_settings,
650 				dpcd_lane_status,
651 				&dpcd_lane_status_updated,
652 				dpcd_lane_adjust,
653 				hop);
654 		if (status != DC_OK) {
655 			result = LINK_TRAINING_ABORT;
656 			break;
657 		}
658 
659 		/* CR can still fail during EQ phase. Fail training if CR fails. */
660 		if (!dp_is_cr_done(lane_count, dpcd_lane_status)) {
661 			result = LINK_TRAINING_EQ_FAIL_CR;
662 			break;
663 		}
664 
665 		if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
666 		    dp_is_symbol_locked(link->cur_link_settings.lane_count, dpcd_lane_status) &&
667 		    dp_is_interlane_aligned(dpcd_lane_status_updated)) {
668 			result =  LINK_TRAINING_SUCCESS;
669 			break;
670 		}
671 
672 		/* Update VS/PE. */
673 		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
674 				lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
675 	}
676 
677 	/* Abort link training if equalization failed due to HPD unplug. */
678 	if (!link->hpd_status)
679 		result = LINK_TRAINING_ABORT;
680 
681 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) equalization\n"
682 		" - hop(%d)\n - result(%d)\n - retries(%d)\n",
683 		__func__,
684 		link->link_id.enum_id - ENUM_ID_1,
685 		hop,
686 		result,
687 		retries_eq);
688 
689 	return result;
690 }
691 
692 /* Execute equalization phase of link training for specified hop in display
693  * path in transparent LTTPR mode:
694  * - driver only issues DPCD transactions leaves USB4 tunneling (SET_CONFIG) messages to DPIA.
695  * - driver writes TPSx to DPCD to notify DPIA that is in equalization phase.
696  * - equalization (EQ) for link is handled by DPOA, which reports result to DPIA on completion.
697  * - DPIA communicates result to driver by updating EQ status when driver reads DPCD.
698  *
699  * @param link DPIA link being trained.
700  * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
701  * @param hop The Hop in display path. DPRX = 0.
702  */
dpia_training_eq_transparent(struct dc_link * link,struct link_training_settings * lt_settings)703 static enum link_training_result dpia_training_eq_transparent(struct dc_link *link,
704 		struct link_training_settings *lt_settings)
705 {
706 	enum link_training_result result = LINK_TRAINING_EQ_FAIL_EQ;
707 	uint32_t retries_eq = 0;
708 	enum dc_status status;
709 	enum dc_dp_training_pattern tr_pattern = lt_settings->pattern_for_eq;
710 	uint32_t wait_time_microsec;
711 	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
712 	union lane_align_status_updated dpcd_lane_status_updated = { {0} };
713 	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = { { {0} } };
714 	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = { { {0} } };
715 
716 	wait_time_microsec = dpia_get_eq_aux_rd_interval(link, lt_settings, DPRX);
717 
718 	for (retries_eq = 0; retries_eq < LINK_TRAINING_MAX_RETRY_COUNT; retries_eq++) {
719 		if (retries_eq == 0) {
720 			status = dpcd_set_lt_pattern(link, tr_pattern, DPRX);
721 			if (status != DC_OK) {
722 				result = LINK_TRAINING_ABORT;
723 				break;
724 			}
725 		}
726 
727 		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
728 
729 		/* Read status and adjustment requests from DPCD. */
730 		status = dp_get_lane_status_and_lane_adjust(
731 				link,
732 				lt_settings,
733 				dpcd_lane_status,
734 				&dpcd_lane_status_updated,
735 				dpcd_lane_adjust,
736 				DPRX);
737 		if (status != DC_OK) {
738 			result = LINK_TRAINING_ABORT;
739 			break;
740 		}
741 
742 		/* CR can still fail during EQ phase. Fail training if CR fails. */
743 		if (!dp_is_cr_done(lane_count, dpcd_lane_status)) {
744 			result = LINK_TRAINING_EQ_FAIL_CR;
745 			break;
746 		}
747 
748 		if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
749 		    dp_is_symbol_locked(link->cur_link_settings.lane_count, dpcd_lane_status) &&
750 		    dp_is_interlane_aligned(dpcd_lane_status_updated)) {
751 			result =  LINK_TRAINING_SUCCESS;
752 			break;
753 		}
754 
755 		/* Update VS/PE. */
756 		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
757 				lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
758 	}
759 
760 	/* Abort link training if equalization failed due to HPD unplug. */
761 	if (!link->hpd_status)
762 		result = LINK_TRAINING_ABORT;
763 
764 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) equalization\n"
765 		" - hop(%d)\n - result(%d)\n - retries(%d)\n",
766 		__func__,
767 		link->link_id.enum_id - ENUM_ID_1,
768 		DPRX,
769 		result,
770 		retries_eq);
771 
772 	return result;
773 }
774 
775 /* Execute equalization phase of link training for specified hop in display
776  * path.
777  *
778  * @param link DPIA link being trained.
779  * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
780  * @param hop The Hop in display path. DPRX = 0.
781  */
dpia_training_eq_phase(struct dc_link * link,struct link_training_settings * lt_settings,uint32_t hop)782 static enum link_training_result dpia_training_eq_phase(struct dc_link *link,
783 		struct link_training_settings *lt_settings,
784 		uint32_t hop)
785 {
786 	enum link_training_result result;
787 
788 	if (link->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT)
789 		result = dpia_training_eq_non_transparent(link, lt_settings, hop);
790 	else
791 		result = dpia_training_eq_transparent(link, lt_settings);
792 
793 	return result;
794 }
795 
796 /* End training of specified hop in display path. */
dpcd_clear_lt_pattern(struct dc_link * link,uint32_t hop)797 static enum dc_status dpcd_clear_lt_pattern(struct dc_link *link, uint32_t hop)
798 {
799 	union dpcd_training_pattern dpcd_pattern = { {0} };
800 	uint32_t dpcd_tps_offset = DP_TRAINING_PATTERN_SET;
801 	enum dc_status status;
802 
803 	if (hop != DPRX)
804 		dpcd_tps_offset = DP_TRAINING_PATTERN_SET_PHY_REPEATER1 +
805 			((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (hop - 1));
806 
807 	status = core_link_write_dpcd(link,
808 			dpcd_tps_offset,
809 			&dpcd_pattern.raw,
810 			sizeof(dpcd_pattern.raw));
811 
812 	return status;
813 }
814 
815 /* End training of specified hop in display path.
816  *
817  * In transparent LTTPR mode:
818  * - driver clears training pattern for the specified hop in DPCD.
819  * In non-transparent LTTPR mode:
820  * - in addition to clearing training pattern, driver issues USB4 tunneling
821  * (SET_CONFIG) messages to notify DPOA when training is done for first hop
822  * (DPTX-to-DPIA) and last hop (DPRX).
823  *
824  * @param link DPIA link being trained.
825  * @param hop The Hop in display path. DPRX = 0.
826  */
dpia_training_end(struct dc_link * link,uint32_t hop)827 static enum link_training_result dpia_training_end(struct dc_link *link,
828 		uint32_t hop)
829 {
830 	enum link_training_result result = LINK_TRAINING_SUCCESS;
831 	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
832 	enum dc_status status;
833 
834 	if (link->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) {
835 		repeater_cnt = dp_convert_to_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
836 
837 		if (hop == repeater_cnt) { /* DPTX-to-DPIA */
838 			/* Send SET_CONFIG(SET_TRAINING:0xff) to notify DPOA that
839 			 * DPTX-to-DPIA hop trained. No DPCD write needed for first hop.
840 			 */
841 			status = core_link_send_set_config(link,
842 					DPIA_SET_CFG_SET_TRAINING,
843 					DPIA_TS_UFP_DONE);
844 			if (status != DC_OK)
845 				result = LINK_TRAINING_ABORT;
846 		} else { /* DPOA-to-x */
847 			/* Write 0x0 to TRAINING_PATTERN_SET */
848 			status = dpcd_clear_lt_pattern(link, hop);
849 			if (status != DC_OK)
850 				result = LINK_TRAINING_ABORT;
851 		}
852 
853 		/* Notify DPOA that non-transparent link training of DPRX done. */
854 		if (hop == DPRX && result != LINK_TRAINING_ABORT) {
855 			status = core_link_send_set_config(link,
856 					DPIA_SET_CFG_SET_TRAINING,
857 					DPIA_TS_DPRX_DONE);
858 			if (status != DC_OK)
859 				result = LINK_TRAINING_ABORT;
860 		}
861 
862 	} else { /* non-LTTPR or transparent LTTPR. */
863 		/* Write 0x0 to TRAINING_PATTERN_SET */
864 		status = dpcd_clear_lt_pattern(link, hop);
865 		if (status != DC_OK)
866 			result = LINK_TRAINING_ABORT;
867 	}
868 
869 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) end\n - hop(%d)\n - result(%d)\n - LTTPR mode(%d)\n",
870 				__func__,
871 				link->link_id.enum_id - ENUM_ID_1,
872 				hop,
873 				result,
874 				link->lttpr_mode);
875 
876 	return result;
877 }
878 
879 /* When aborting training of specified hop in display path, clean up by:
880  * - Attempting to clear DPCD TRAINING_PATTERN_SET, LINK_BW_SET and LANE_COUNT_SET.
881  * - Sending SET_CONFIG(SET_LINK) with lane count and link rate set to 0.
882  *
883  * @param link DPIA link being trained.
884  * @param hop The Hop in display path. DPRX = 0.
885  */
dpia_training_abort(struct dc_link * link,uint32_t hop)886 static void dpia_training_abort(struct dc_link *link, uint32_t hop)
887 {
888 	uint8_t data = 0;
889 	uint32_t dpcd_tps_offset = DP_TRAINING_PATTERN_SET;
890 
891 	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) aborting\n - LTTPR mode(%d)\n - HPD(%d)\n",
892 				__func__,
893 				link->link_id.enum_id - ENUM_ID_1,
894 				link->lttpr_mode,
895 				link->hpd_status);
896 
897 	/* Abandon clean-up if sink unplugged. */
898 	if (!link->hpd_status)
899 		return;
900 
901 	if (hop != DPRX)
902 		dpcd_tps_offset = DP_TRAINING_PATTERN_SET_PHY_REPEATER1 +
903 			((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (hop - 1));
904 
905 	core_link_write_dpcd(link, dpcd_tps_offset, &data, 1);
906 	core_link_write_dpcd(link, DP_LINK_BW_SET, &data, 1);
907 	core_link_write_dpcd(link, DP_LANE_COUNT_SET, &data, 1);
908 	core_link_send_set_config(link, DPIA_SET_CFG_SET_LINK, data);
909 }
910 
dc_link_dpia_perform_link_training(struct dc_link * link,const struct dc_link_settings * link_setting,bool skip_video_pattern)911 enum link_training_result dc_link_dpia_perform_link_training(struct dc_link *link,
912 	const struct dc_link_settings *link_setting,
913 	bool skip_video_pattern)
914 {
915 	enum link_training_result result;
916 	struct link_training_settings lt_settings;
917 	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
918 	int8_t repeater_id; /* Current hop. */
919 
920 	/* Configure link as prescribed in link_setting and set LTTPR mode. */
921 	result = dpia_configure_link(link, link_setting, &lt_settings);
922 	if (result != LINK_TRAINING_SUCCESS)
923 		return result;
924 
925 	if (link->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT)
926 		repeater_cnt = dp_convert_to_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
927 
928 	/* Train each hop in turn starting with the one closest to DPTX.
929 	 * In transparent or non-LTTPR mode, train only the final hop (DPRX).
930 	 */
931 	for (repeater_id = repeater_cnt; repeater_id >= 0; repeater_id--) {
932 		/* Clock recovery. */
933 		result = dpia_training_cr_phase(link, &lt_settings, repeater_id);
934 		if (result != LINK_TRAINING_SUCCESS)
935 			break;
936 
937 		/* Equalization. */
938 		result = dpia_training_eq_phase(link, &lt_settings, repeater_id);
939 		if (result != LINK_TRAINING_SUCCESS)
940 			break;
941 
942 		/* Stop training hop. */
943 		result = dpia_training_end(link, repeater_id);
944 		if (result != LINK_TRAINING_SUCCESS)
945 			break;
946 	}
947 
948 	/* Double-check link status if training successful; gracefully abort
949 	 * training of current hop if training failed due to message tunneling
950 	 * failure; end training of hop if training ended conventionally and
951 	 * falling back to lower bandwidth settings possible.
952 	 */
953 	if (result == LINK_TRAINING_SUCCESS) {
954 		msleep(5);
955 		result = dp_check_link_loss_status(link, &lt_settings);
956 	} else if (result == LINK_TRAINING_ABORT) {
957 		dpia_training_abort(link, repeater_id);
958 	} else {
959 		dpia_training_end(link, repeater_id);
960 	}
961 	return result;
962 }
963