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