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. */ 55 enum 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. */ 63 enum 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. */ 73 union 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 */ 100 static 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, <_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, fec_enable); 140 if (status != DC_OK && link->is_hpd_pending) 141 return LINK_TRAINING_ABORT; 142 143 return LINK_TRAINING_SUCCESS; 144 } 145 146 static 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(link->ctx, 160 link, &payload, &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. */ 169 static 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. */ 202 static 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. */ 233 static 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 dpcd_tps_offset, 269 &dpcd_pattern.raw, 270 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 */ 286 static 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(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 DPIA_SET_CFG_SET_LINK, 325 link, 326 lt_settings); 327 status = core_link_send_set_config( 328 link, 329 DPIA_SET_CFG_SET_LINK, 330 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(lt_settings->pattern_for_cr, &ts); 345 if (status != DC_OK) { 346 result = LINK_TRAINING_ABORT; 347 break; 348 } 349 status = core_link_send_set_config( 350 link, 351 DPIA_SET_CFG_SET_TRAINING, 352 ts); 353 if (status != DC_OK) { 354 result = LINK_TRAINING_ABORT; 355 break; 356 } 357 status = dpcd_set_lt_pattern(link, 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 DPIA_SET_CFG_SET_VSPE, 370 link, 371 lt_settings); 372 status = core_link_send_set_config( 373 link, 374 DPIA_SET_CFG_SET_VSPE, 375 set_cfg_data); 376 if (status != DC_OK) { 377 result = LINK_TRAINING_ABORT; 378 break; 379 } 380 } 381 status = dpcd_set_lane_settings(link, lt_settings, 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_time_microsec); 388 389 /* Read status and adjustment requests from DPCD. */ 390 status = dp_get_lane_status_and_lane_adjust( 391 link, 392 lt_settings, 393 dpcd_lane_status, 394 &dpcd_lane_status_updated, 395 dpcd_lane_adjust, 396 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(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(lane_count, dpcd_lane_status); 410 411 if (dp_is_max_vs_reached(lt_settings)) 412 break; 413 414 /* Count number of attempts with same drive settings. 415 * Note: settings are the same for all lanes, 416 * so comparing first lane is sufficient. 417 */ 418 if ((lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET == 419 dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE) 420 && (lt_settings->dpcd_lane_settings[0].bits.PRE_EMPHASIS_SET == 421 dpcd_lane_adjust[0].bits.PRE_EMPHASIS_LANE)) 422 retries_cr++; 423 else 424 retries_cr = 0; 425 426 /* Update VS/PE. */ 427 dp_decide_lane_settings(lt_settings, dpcd_lane_adjust, 428 lt_settings->hw_lane_settings, 429 lt_settings->dpcd_lane_settings); 430 retry_count++; 431 } 432 433 /* Abort link training if clock recovery failed due to HPD unplug. */ 434 if (link->is_hpd_pending) 435 result = LINK_TRAINING_ABORT; 436 437 DC_LOG_HW_LINK_TRAINING( 438 "%s\n DPIA(%d) clock recovery\n -hop(%d)\n - result(%d)\n - retries(%d)\n - status(%d)\n", 439 __func__, 440 link->link_id.enum_id - ENUM_ID_1, 441 hop, 442 result, 443 retry_count, 444 status); 445 446 return result; 447 } 448 449 /* Execute clock recovery phase of link training in transparent LTTPR mode: 450 * - Driver only issues DPCD transactions and leaves USB4 tunneling (SET_CONFIG) messages to DPIA. 451 * - Driver writes TPS1 to DPCD to kick off training. 452 * - Clock recovery (CR) for link is handled by DPOA, which reports result to DPIA on completion. 453 * - DPIA communicates result to driver by updating CR status when driver reads DPCD. 454 * 455 * @param link DPIA link being trained. 456 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis). 457 */ 458 static enum link_training_result dpia_training_cr_transparent( 459 struct dc_link *link, 460 const struct link_resource *link_res, 461 struct link_training_settings *lt_settings) 462 { 463 enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0; 464 enum dc_status status; 465 uint32_t retries_cr = 0; /* Number of consecutive attempts with same VS or PE. */ 466 uint32_t retry_count = 0; 467 uint32_t wait_time_microsec = lt_settings->cr_pattern_time; 468 enum dc_lane_count lane_count = lt_settings->link_settings.lane_count; 469 union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0}; 470 union lane_align_status_updated dpcd_lane_status_updated = {0}; 471 union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0}; 472 473 /* Cap of LINK_TRAINING_MAX_CR_RETRY attempts at clock recovery. 474 * Fix inherited from perform_clock_recovery_sequence() - 475 * the DP equivalent of this function: 476 * Required for Synaptics MST hub which can put the LT in 477 * infinite loop by switching the VS between level 0 and level 1 478 * continuously. 479 */ 480 while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) && 481 (retry_count < LINK_TRAINING_MAX_CR_RETRY)) { 482 483 /* Write TPS1 (not VS or PE) to DPCD to start CR phase. 484 * DPIA sends SET_CONFIG(SET_LINK) to notify DPOA to 485 * start link training. 486 */ 487 if (retry_count == 0) { 488 status = dpcd_set_lt_pattern(link, lt_settings->pattern_for_cr, DPRX); 489 if (status != DC_OK) { 490 result = LINK_TRAINING_ABORT; 491 break; 492 } 493 } 494 495 dp_wait_for_training_aux_rd_interval(link, wait_time_microsec); 496 497 /* Read status and adjustment requests from DPCD. */ 498 status = dp_get_lane_status_and_lane_adjust( 499 link, 500 lt_settings, 501 dpcd_lane_status, 502 &dpcd_lane_status_updated, 503 dpcd_lane_adjust, 504 DPRX); 505 if (status != DC_OK) { 506 result = LINK_TRAINING_ABORT; 507 break; 508 } 509 510 /* Check if clock recovery successful. */ 511 if (dp_is_cr_done(lane_count, dpcd_lane_status)) { 512 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(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, dpcd_lane_adjust, 536 lt_settings->hw_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 */ 561 static 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. */ 578 static 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 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 */ 611 static 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; 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(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(tr_pattern, &ts); 651 if (status != DC_OK) { 652 result = LINK_TRAINING_ABORT; 653 break; 654 } 655 status = core_link_send_set_config( 656 link, 657 DPIA_SET_CFG_SET_TRAINING, 658 ts); 659 if (status != DC_OK) { 660 result = LINK_TRAINING_ABORT; 661 break; 662 } 663 status = dpcd_set_lt_pattern(link, 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 DPIA_SET_CFG_SET_VSPE, 676 link, 677 lt_settings); 678 status = core_link_send_set_config( 679 link, 680 DPIA_SET_CFG_SET_VSPE, 681 set_cfg_data); 682 if (status != DC_OK) { 683 result = LINK_TRAINING_ABORT; 684 break; 685 } 686 } 687 status = dpcd_set_lane_settings(link, lt_settings, 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_time_microsec); 702 703 /* Read status and adjustment requests from DPCD. */ 704 status = dp_get_lane_status_and_lane_adjust( 705 link, 706 lt_settings, 707 dpcd_lane_status, 708 &dpcd_lane_status_updated, 709 dpcd_lane_adjust, 710 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(lane_count, dpcd_lane_status)) { 718 result = LINK_TRAINING_EQ_FAIL_CR; 719 break; 720 } 721 722 if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) && 723 dp_is_symbol_locked(link->cur_link_settings.lane_count, dpcd_lane_status) && 724 dp_is_interlane_aligned(dpcd_lane_status_updated)) { 725 result = LINK_TRAINING_SUCCESS; 726 break; 727 } 728 729 /* Update VS/PE. */ 730 dp_decide_lane_settings(lt_settings, dpcd_lane_adjust, 731 lt_settings->hw_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 */ 761 static 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, 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, tr_pattern, 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_time_microsec); 789 790 /* Read status and adjustment requests from DPCD. */ 791 status = dp_get_lane_status_and_lane_adjust( 792 link, 793 lt_settings, 794 dpcd_lane_status, 795 &dpcd_lane_status_updated, 796 dpcd_lane_adjust, 797 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(lane_count, dpcd_lane_status)) { 805 result = LINK_TRAINING_EQ_FAIL_CR; 806 break; 807 } 808 809 if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) && 810 dp_is_symbol_locked(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(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, dpcd_lane_adjust, 822 lt_settings->hw_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 */ 846 static 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. */ 863 static 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 dpcd_tps_offset, 878 &dpcd_pattern.raw, 879 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 */ 896 static 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(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 DPIA_SET_CFG_SET_TRAINING, 916 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 DPIA_SET_CFG_SET_TRAINING, 931 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 */ 962 static 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, dpcd_tps_offset, &data, 1); 985 core_link_write_dpcd(link, DP_LINK_BW_SET, &data, 1); 986 core_link_write_dpcd(link, DP_LANE_COUNT_SET, &data, 1); 987 core_link_send_set_config(link, DPIA_SET_CFG_SET_LINK, data); 988 } 989 990 enum 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_settings); 1004 1005 /* Configure link as prescribed in link_setting and set LTTPR mode. */ 1006 result = dpia_configure_link(link, link_res, link_setting, <_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(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, <_settings, repeater_id); 1019 if (result != LINK_TRAINING_SUCCESS) 1020 break; 1021 1022 /* Equalization. */ 1023 result = dpia_training_eq_phase(link, link_res, <_settings, repeater_id); 1024 if (result != LINK_TRAINING_SUCCESS) 1025 break; 1026 1027 /* Stop training hop. */ 1028 result = dpia_training_end(link, <_settings, 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(5000); 1040 if (!link->skip_fallback_on_link_loss) 1041 result = dp_check_link_loss_status(link, <_settings); 1042 } else if (result == LINK_TRAINING_ABORT) 1043 dpia_training_abort(link, <_settings, repeater_id); 1044 else 1045 dpia_training_end(link, <_settings, repeater_id); 1046 1047 return result; 1048 } 1049