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