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

source code of linux/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training_dpia.c