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