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