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