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