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