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