root/sys/dev/pci/drm/amd/display/dc/link/protocols/link_dp_training_8b_10b.c
/*
 * Copyright 2022 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: AMD
 *
 */

/* FILE POLICY AND INTENDED USAGE:
 * This file implements dp 8b/10b link training software policies and
 * sequences.
 */
#include "link_dp_training_8b_10b.h"
#include "link_dpcd.h"
#include "link_dp_phy.h"
#include "link_dp_capability.h"

#define DC_LOGGER \
        link->ctx->logger

static void get_default_8b_10b_lttpr_aux_rd_interval(
                union training_aux_rd_interval *training_rd_interval)
{
        /* LTTPR are required to program DPCD 0000Eh to 0x4 (16ms) upon AUX
         * read reply to this register. Since old sinks with DPCD rev 1.1
         * and earlier may not support this register, assume the mandatory
         * value is programmed by the LTTPR to avoid AUX timeout issues.
         */
        training_rd_interval->raw = 0x4;
}

static int32_t get_cr_training_aux_rd_interval(struct dc_link *link,
                const struct dc_link_settings *link_settings,
                enum lttpr_mode lttpr_mode)
{
        union training_aux_rd_interval training_rd_interval;
        uint32_t wait_in_micro_secs = 100;

        memset(&training_rd_interval, 0, sizeof(training_rd_interval));
        if (link_dp_get_encoding_format(link_settings) == DP_8b_10b_ENCODING) {
                if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12)
                        core_link_read_dpcd(
                                        link,
                                        DP_TRAINING_AUX_RD_INTERVAL,
                                        (uint8_t *)&training_rd_interval,
                                        sizeof(training_rd_interval));
                else if (dp_is_lttpr_present(link))
                        get_default_8b_10b_lttpr_aux_rd_interval(&training_rd_interval);

                if (training_rd_interval.raw != 0) {
                        if (lttpr_mode != LTTPR_MODE_NON_TRANSPARENT)
                                wait_in_micro_secs = 400;
                        if (training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL)
                                wait_in_micro_secs = training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL * 4000;
                }
        }
        return wait_in_micro_secs;
}

static uint32_t get_eq_training_aux_rd_interval(
        struct dc_link *link,
        const struct dc_link_settings *link_settings)
{
        union training_aux_rd_interval training_rd_interval;

        memset(&training_rd_interval, 0, sizeof(training_rd_interval));
        if (link_dp_get_encoding_format(link_settings) == DP_128b_132b_ENCODING) {
                core_link_read_dpcd(
                                link,
                                DP_128B132B_TRAINING_AUX_RD_INTERVAL,
                                (uint8_t *)&training_rd_interval,
                                sizeof(training_rd_interval));
        } else if (link_dp_get_encoding_format(link_settings) == DP_8b_10b_ENCODING) {
                if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12)
                        core_link_read_dpcd(
                                        link,
                                        DP_TRAINING_AUX_RD_INTERVAL,
                                        (uint8_t *)&training_rd_interval,
                                        sizeof(training_rd_interval));
                else if (dp_is_lttpr_present(link))
                        get_default_8b_10b_lttpr_aux_rd_interval(&training_rd_interval);
        }

        switch (training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL) {
        case 0: return 400;
        case 1: return 4000;
        case 2: return 8000;
        case 3: return 12000;
        case 4: return 16000;
        case 5: return 32000;
        case 6: return 64000;
        default: return 400;
        }
}

void decide_8b_10b_training_settings(
        struct dc_link *link,
        const struct link_resource *link_res,
        const struct dc_link_settings *link_setting,
        struct link_training_settings *lt_settings)
{
        memset(lt_settings, '\0', sizeof(struct link_training_settings));

        /* Initialize link settings */
        lt_settings->link_settings.use_link_rate_set = link_setting->use_link_rate_set;
        lt_settings->link_settings.link_rate_set = link_setting->link_rate_set;
        lt_settings->link_settings.link_rate = link_setting->link_rate;
        lt_settings->link_settings.lane_count = link_setting->lane_count;
        /* TODO hard coded to SS for now
         * lt_settings.link_settings.link_spread =
         * dal_display_path_is_ss_supported(
         * path_mode->display_path) ?
         * LINK_SPREAD_05_DOWNSPREAD_30KHZ :
         * LINK_SPREAD_DISABLED;
         */
        lt_settings->link_settings.link_spread = link->dp_ss_off ?
                        LINK_SPREAD_DISABLED : LINK_SPREAD_05_DOWNSPREAD_30KHZ;
        lt_settings->eq_pattern_time = get_eq_training_aux_rd_interval(link, link_setting);
        lt_settings->pattern_for_cr = decide_cr_training_pattern(link_setting);
        lt_settings->pattern_for_eq = decide_eq_training_pattern(link, link_res, link_setting);
        lt_settings->enhanced_framing = 1;
        lt_settings->should_set_fec_ready = true;
        lt_settings->disallow_per_lane_settings = true;
        lt_settings->always_match_dpcd_with_hw_lane_settings = true;
        lt_settings->lttpr_mode = dp_decide_8b_10b_lttpr_mode(link);
        lt_settings->cr_pattern_time = get_cr_training_aux_rd_interval(link, link_setting, lt_settings->lttpr_mode);
        dp_hw_to_dpcd_lane_settings(lt_settings, lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);

        /* Some embedded LTTPRs rely on receiving TPS2 before LT to interop reliably with sensitive VGA dongles
         * This allows these LTTPRs to minimize freq/phase and skew variation during lock and deskew sequences
         */
        if ((link->chip_caps & AMD_EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK) ==
                        AMD_EXT_DISPLAY_PATH_CAPS__DP_EARLY_8B10B_TPS2) {
                lt_settings->lttpr_early_tps2 = true;
        }
}

enum lttpr_mode dp_decide_8b_10b_lttpr_mode(struct dc_link *link)
{
        bool is_lttpr_present = dp_is_lttpr_present(link);
        bool vbios_lttpr_force_non_transparent = link->dc->caps.vbios_lttpr_enable;
        bool vbios_lttpr_aware = link->dc->caps.vbios_lttpr_aware;

        if (!is_lttpr_present)
                return LTTPR_MODE_NON_LTTPR;

        if (vbios_lttpr_aware) {
                if (vbios_lttpr_force_non_transparent) {
                        DC_LOG_DC("chose LTTPR_MODE_NON_TRANSPARENT due to VBIOS DCE_INFO_CAPS_LTTPR_SUPPORT_ENABLE set to 1.\n");
                        return LTTPR_MODE_NON_TRANSPARENT;
                } else {
                        DC_LOG_DC("chose LTTPR_MODE_NON_TRANSPARENT by default due to VBIOS not set DCE_INFO_CAPS_LTTPR_SUPPORT_ENABLE set to 1.\n");
                        return LTTPR_MODE_TRANSPARENT;
                }
        }

        if (link->dc->config.allow_lttpr_non_transparent_mode.bits.DP1_4A &&
                        link->dc->caps.extended_aux_timeout_support) {
                DC_LOG_DC("chose LTTPR_MODE_NON_TRANSPARENT by default and dc->config.allow_lttpr_non_transparent_mode.bits.DP1_4A set to 1.\n");
                return LTTPR_MODE_NON_TRANSPARENT;
        }

        DC_LOG_DC("chose LTTPR_MODE_NON_LTTPR.\n");
        return LTTPR_MODE_NON_LTTPR;
}

static void set_link_settings_and_perform_early_tps2_retimer_pre_lt_sequence(struct dc_link *link,
        const struct link_resource *link_res,
        struct link_training_settings *lt_settings,
        uint32_t lttpr_count)
{
        /* Vendor-specific LTTPR early TPS2 sequence:
        * 1. Output TPS2
        * 2. Wait 400us
        * 3. Set link settings as usual
        * 4. Write TPS1 to DP_TRAINING_PATTERN_SET_PHY_REPEATERx targeting LTTPR closest to host
        * 5. Wait 1ms
        * 6. Begin link training as usual
        * */

        uint32_t closest_lttpr_address_offset = dp_get_closest_lttpr_offset(lttpr_count);

        union dpcd_training_pattern dpcd_pattern = {0};

        dpcd_pattern.v1_4.TRAINING_PATTERN_SET = 1;
        dpcd_pattern.v1_4.SCRAMBLING_DISABLE = 1;

        DC_LOG_HW_LINK_TRAINING("%s\n GPU sends TPS2. Wait 400us.\n", __func__);

        dp_set_hw_training_pattern(link, link_res, DP_TRAINING_PATTERN_SEQUENCE_2, DPRX);

        dp_set_hw_lane_settings(link, link_res, lt_settings, DPRX);

        udelay(400);

        dpcd_set_link_settings(link, lt_settings);

        core_link_write_dpcd(link, DP_TRAINING_PATTERN_SET_PHY_REPEATER1 + closest_lttpr_address_offset, &dpcd_pattern.raw, 1);

        udelay(1000);
        }

enum link_training_result perform_8b_10b_clock_recovery_sequence(
        struct dc_link *link,
        const struct link_resource *link_res,
        struct link_training_settings *lt_settings,
        uint32_t offset)
{
        enum dc_status status;
        uint32_t retries_cr;
        uint32_t retry_count;
        uint32_t wait_time_microsec;
        enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
        union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX];
        union lane_align_status_updated dpcd_lane_status_updated;
        union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};

        retries_cr = 0;
        retry_count = 0;

        memset(&dpcd_lane_status, '\0', sizeof(dpcd_lane_status));
        memset(&dpcd_lane_status_updated, '\0',
        sizeof(dpcd_lane_status_updated));

        if (!link->ctx->dc->work_arounds.lt_early_cr_pattern)
                dp_set_hw_training_pattern(link, link_res, lt_settings->pattern_for_cr, offset);

        /* najeeb - The synaptics MST hub can put the LT in
        * infinite loop by switching the VS
        */
        /* between level 0 and level 1 continuously, here
        * we try for CR lock for LinkTrainingMaxCRRetry count*/
        while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
                (retry_count < LINK_TRAINING_MAX_CR_RETRY)) {


                /* 1. call HWSS to set lane settings*/
                dp_set_hw_lane_settings(
                                link,
                                link_res,
                                lt_settings,
                                offset);

                /* 2. update DPCD of the receiver*/
                if (!retry_count)
                        /* EPR #361076 - write as a 5-byte burst,
                         * but only for the 1-st iteration.*/
                        dpcd_set_lt_pattern_and_lane_settings(
                                        link,
                                        lt_settings,
                                        lt_settings->pattern_for_cr,
                                        offset);
                else
                        dpcd_set_lane_settings(
                                        link,
                                        lt_settings,
                                        offset);

                /* 3. wait receiver to lock-on*/
                wait_time_microsec = lt_settings->cr_pattern_time;

                dp_wait_for_training_aux_rd_interval(
                                link,
                                wait_time_microsec);

                /* 4. Read lane status and requested drive
                * settings as set by the sink
                */
                status = dp_get_lane_status_and_lane_adjust(
                                link,
                                lt_settings,
                                dpcd_lane_status,
                                &dpcd_lane_status_updated,
                                dpcd_lane_adjust,
                                offset);

                if (dp_check_dpcd_reqeust_status(link, status))
                        return LINK_TRAINING_ABORT;

                /* 5. check CR done*/
                if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
                        DC_LOG_HW_LINK_TRAINING("%s: Clock recovery OK\n", __func__);
                        return LINK_TRAINING_SUCCESS;
                }

                /* 6. max VS reached*/
                if ((link_dp_get_encoding_format(&lt_settings->link_settings) ==
                                DP_8b_10b_ENCODING) &&
                                dp_is_max_vs_reached(lt_settings))
                        break;

                /* 7. same lane settings*/
                /* Note: settings are the same for all lanes,
                 * so comparing first lane is sufficient*/
                if ((link_dp_get_encoding_format(&lt_settings->link_settings) == DP_8b_10b_ENCODING) &&
                                lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
                                                dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
                        retries_cr++;
                else if ((link_dp_get_encoding_format(&lt_settings->link_settings) == DP_128b_132b_ENCODING) &&
                                lt_settings->dpcd_lane_settings[0].tx_ffe.PRESET_VALUE ==
                                                dpcd_lane_adjust[0].tx_ffe.PRESET_VALUE)
                        retries_cr++;
                else
                        retries_cr = 0;

                /* 8. update VS/PE/PC2 in lt_settings*/
                dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
                                lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
                retry_count++;
        }

        if (retry_count >= LINK_TRAINING_MAX_CR_RETRY) {
                ASSERT(0);
                DC_LOG_ERROR("%s: Link Training Error, could not get CR after %d tries. Possibly voltage swing issue",
                        __func__,
                        LINK_TRAINING_MAX_CR_RETRY);

        }

        return dp_get_cr_failure(lane_count, dpcd_lane_status);
}

enum link_training_result perform_8b_10b_channel_equalization_sequence(
        struct dc_link *link,
        const struct link_resource *link_res,
        struct link_training_settings *lt_settings,
        uint32_t offset)
{
        enum dc_status status;
        enum dc_dp_training_pattern tr_pattern;
        uint32_t retries_ch_eq;
        uint32_t wait_time_microsec;
        enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
        union lane_align_status_updated dpcd_lane_status_updated = {0};
        union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
        union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};

        /* Note: also check that TPS4 is a supported feature*/
        tr_pattern = lt_settings->pattern_for_eq;

        if (is_repeater(lt_settings, offset) && link_dp_get_encoding_format(&lt_settings->link_settings) == DP_8b_10b_ENCODING)
                tr_pattern = DP_TRAINING_PATTERN_SEQUENCE_4;

        dp_set_hw_training_pattern(link, link_res, tr_pattern, offset);

        for (retries_ch_eq = 0; retries_ch_eq <= LINK_TRAINING_MAX_RETRY_COUNT;
                retries_ch_eq++) {

                dp_set_hw_lane_settings(link, link_res, lt_settings, offset);

                /* 2. update DPCD*/
                if (!retries_ch_eq)
                        /* EPR #361076 - write as a 5-byte burst,
                         * but only for the 1-st iteration
                         */

                        dpcd_set_lt_pattern_and_lane_settings(
                                link,
                                lt_settings,
                                tr_pattern, offset);
                else
                        dpcd_set_lane_settings(link, lt_settings, offset);

                /* 3. wait for receiver to lock-on*/
                wait_time_microsec = dp_get_eq_aux_rd_interval(link, lt_settings, offset, retries_ch_eq);

                dp_wait_for_training_aux_rd_interval(
                                link,
                                wait_time_microsec);

                /* 4. Read lane status and requested
                 * drive settings as set by the sink*/

                status = dp_get_lane_status_and_lane_adjust(
                        link,
                        lt_settings,
                        dpcd_lane_status,
                        &dpcd_lane_status_updated,
                        dpcd_lane_adjust,
                        offset);

                if (dp_check_dpcd_reqeust_status(link, status))
                        return LINK_TRAINING_ABORT;

                /* 5. check CR done*/
                if (!dp_is_cr_done(lane_count, dpcd_lane_status))
                        return dpcd_lane_status[0].bits.CR_DONE_0 ?
                                        LINK_TRAINING_EQ_FAIL_CR_PARTIAL :
                                        LINK_TRAINING_EQ_FAIL_CR;

                /* 6. check CHEQ done*/
                if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
                                dp_is_symbol_locked(lane_count, dpcd_lane_status) &&
                                dp_check_interlane_aligned(dpcd_lane_status_updated, link, retries_ch_eq))
                        return LINK_TRAINING_SUCCESS;

                /* 7. update VS/PE/PC2 in lt_settings*/
                dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
                                lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
        }

        return LINK_TRAINING_EQ_FAIL_EQ;

}

enum link_training_result dp_perform_8b_10b_link_training(
                struct dc_link *link,
                const struct link_resource *link_res,
                struct link_training_settings *lt_settings)
{
        enum link_training_result status = LINK_TRAINING_SUCCESS;

        uint8_t repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
        uint8_t repeater_id;
        uint8_t lane = 0;

        if (link->ctx->dc->work_arounds.lt_early_cr_pattern)
                start_clock_recovery_pattern_early(link, link_res, lt_settings, DPRX);

        /* 1. set link rate, lane count and spread. */
        if (lt_settings->lttpr_early_tps2)
                set_link_settings_and_perform_early_tps2_retimer_pre_lt_sequence(link, link_res, lt_settings, repeater_cnt);
        else
                dpcd_set_link_settings(link, lt_settings);

        if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) {

                /* 2. perform link training (set link training done
                 *  to false is done as well)
                 */

                for (repeater_id = repeater_cnt; (repeater_id > 0 && status == LINK_TRAINING_SUCCESS);
                                repeater_id--) {
                        status = perform_8b_10b_clock_recovery_sequence(link, link_res, lt_settings, repeater_id);

                        if (status != LINK_TRAINING_SUCCESS) {
                                repeater_training_done(link, repeater_id);
                                break;
                        }

                        status = perform_8b_10b_channel_equalization_sequence(link,
                                        link_res,
                                        lt_settings,
                                        repeater_id);
                        if (status == LINK_TRAINING_SUCCESS)
                                DC_LOG_HW_LINK_TRAINING("%s: Channel EQ done.\n", __func__);

                        repeater_training_done(link, repeater_id);

                        if (status != LINK_TRAINING_SUCCESS)
                                break;

                        for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++) {
                                lt_settings->dpcd_lane_settings[lane].raw = 0;
                                lt_settings->hw_lane_settings[lane].VOLTAGE_SWING = 0;
                                lt_settings->hw_lane_settings[lane].PRE_EMPHASIS = 0;
                        }
                }
        }

        if (status == LINK_TRAINING_SUCCESS) {
                status = perform_8b_10b_clock_recovery_sequence(link, link_res, lt_settings, DPRX);
                if (status == LINK_TRAINING_SUCCESS) {
                        status = perform_8b_10b_channel_equalization_sequence(link,
                                        link_res,
                                        lt_settings,
                                        DPRX);
                        if (status == LINK_TRAINING_SUCCESS)
                                DC_LOG_HW_LINK_TRAINING("%s: Channel EQ done.\n", __func__);
                }
        }

        return status;
}