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