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