root/drivers/gpu/drm/amd/display/dc/link/accessories/link_dp_cts.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
 *
 */
#include "link_dp_cts.h"
#include "link/link_resource.h"
#include "link/protocols/link_dpcd.h"
#include "link/protocols/link_dp_training.h"
#include "link/protocols/link_dp_phy.h"
#include "link/protocols/link_dp_training_fixed_vs_pe_retimer.h"
#include "link/protocols/link_dp_capability.h"
#include "link/link_dpms.h"
#include "resource.h"
#include "dm_helpers.h"
#include "dc_dmub_srv.h"
#include "dce/dmub_hw_lock_mgr.h"
#include "clk_mgr.h"

#define DC_LOGGER \
        link->ctx->logger

static enum dc_link_rate get_link_rate_from_test_link_rate(uint8_t test_rate)
{
        switch (test_rate) {
        case DP_TEST_LINK_RATE_RBR:
                return LINK_RATE_LOW;
        case DP_TEST_LINK_RATE_HBR:
                return LINK_RATE_HIGH;
        case DP_TEST_LINK_RATE_HBR2:
                return LINK_RATE_HIGH2;
        case DP_TEST_LINK_RATE_HBR3:
                return LINK_RATE_HIGH3;
        case DP_TEST_LINK_RATE_UHBR10:
                return LINK_RATE_UHBR10;
        case DP_TEST_LINK_RATE_UHBR20:
                return LINK_RATE_UHBR20;
        case DP_TEST_LINK_RATE_UHBR13_5_LEGACY:
        case DP_TEST_LINK_RATE_UHBR13_5:
                return LINK_RATE_UHBR13_5;
        default:
                return LINK_RATE_UNKNOWN;
        }
}

static void dp_retrain_link_dp_test(struct dc_link *link,
                        struct dc_link_settings *link_setting,
                        bool skip_video_pattern)
{
        struct pipe_ctx *pipes[MAX_PIPES];
        struct dc_state *state = link->dc->current_state;
        struct dc_stream_update stream_update = { 0 };
        bool dpms_off = false;
        bool needs_divider_update = false;
        bool was_hpo_acquired = resource_is_hpo_acquired(link->dc->current_state);
        bool is_hpo_acquired;
        uint8_t count;
        int i;
        struct audio_output audio_output[MAX_PIPES];
        struct dc_stream_state *streams_on_link[MAX_PIPES];
        int num_streams_on_link = 0;
        struct dc *dc = (struct dc *)link->dc;

        needs_divider_update = (link->dc->link_srv->dp_get_encoding_format(link_setting) !=
                link->dc->link_srv->dp_get_encoding_format((const struct dc_link_settings *) &link->cur_link_settings))
                || link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA;

        udelay(100);

        link_get_master_pipes_with_dpms_on(link, state, &count, pipes);

        for (i = 0; i < count; i++) {
                link_set_dpms_off(pipes[i]);
                pipes[i]->link_config.dp_link_settings = *link_setting;
                update_dp_encoder_resources_for_test_harness(
                                link->dc,
                                state,
                                pipes[i]);

                // Disable OTG and re-enable after updating clocks
                pipes[i]->stream_res.tg->funcs->disable_crtc(pipes[i]->stream_res.tg);
        }

        if (needs_divider_update && link->dc->res_pool->funcs->update_dc_state_for_encoder_switch) {
                link->dc->res_pool->funcs->update_dc_state_for_encoder_switch(link,
                                link_setting, count,
                                *pipes, &audio_output[0]);
                for (i = 0; i < count; i++) {
                        pipes[i]->clock_source->funcs->program_pix_clk(
                                        pipes[i]->clock_source,
                                        &pipes[i]->stream_res.pix_clk_params,
                                        link->dc->link_srv->dp_get_encoding_format(&pipes[i]->link_config.dp_link_settings),
                                        &pipes[i]->pll_settings);

                        if (pipes[i]->stream_res.audio != NULL) {
                                const struct link_hwss *link_hwss = get_link_hwss(
                                        link, &pipes[i]->link_res);

                                link_hwss->setup_audio_output(pipes[i], &audio_output[i],
                                                pipes[i]->stream_res.audio->inst);

                                pipes[i]->stream_res.audio->funcs->az_configure(
                                                pipes[i]->stream_res.audio,
                                                pipes[i]->stream->signal,
                                                &audio_output[i].crtc_info,
                                                &pipes[i]->stream->audio_info,
                                                &audio_output[i].dp_link_info);

                                if (link->dc->config.disable_hbr_audio_dp2 &&
                                                pipes[i]->stream_res.audio->funcs->az_disable_hbr_audio &&
                                                link->dc->link_srv->dp_is_128b_132b_signal(pipes[i]))
                                        pipes[i]->stream_res.audio->funcs->az_disable_hbr_audio(pipes[i]->stream_res.audio);
                        }
                }
        }

        // Toggle on HPO I/O if necessary
        is_hpo_acquired = resource_is_hpo_acquired(state);
        if (was_hpo_acquired != is_hpo_acquired && link->dc->hwss.setup_hpo_hw_control)
                link->dc->hwss.setup_hpo_hw_control(link->dc->hwseq, is_hpo_acquired);

        for (i = 0; i < count; i++)
                pipes[i]->stream_res.tg->funcs->enable_crtc(pipes[i]->stream_res.tg);

        // Set DPMS on with stream update
        // Cache all streams on current link since dc_update_planes_and_stream might kill current_state
        for (i = 0; i < MAX_PIPES; i++) {
                if (state->streams[i] && state->streams[i]->link && state->streams[i]->link == link)
                        streams_on_link[num_streams_on_link++] = state->streams[i];
        }

        for (i = 0; i < num_streams_on_link; i++) {
                if (streams_on_link[i] && streams_on_link[i]->link && streams_on_link[i]->link == link) {
                        stream_update.stream = streams_on_link[i];
                        stream_update.dpms_off = &dpms_off;
                        dc_update_planes_and_stream(dc, NULL, 0, streams_on_link[i], &stream_update);
                }
        }
}

static void dp_test_send_link_training(struct dc_link *link)
{
        struct dc_link_settings link_settings = {0};
        uint8_t test_rate = 0;

        core_link_read_dpcd(
                        link,
                        DP_TEST_LANE_COUNT,
                        (unsigned char *)(&link_settings.lane_count),
                        1);
        core_link_read_dpcd(
                        link,
                        DP_TEST_LINK_RATE,
                        &test_rate,
                        1);
        link_settings.link_rate = get_link_rate_from_test_link_rate(test_rate);

        if (link_settings.link_rate == LINK_RATE_UNKNOWN) {
                DC_LOG_ERROR("%s: Invalid test link rate.", __func__);
                ASSERT(0);
        }

        /* Set preferred link settings */
        link->verified_link_cap.lane_count = link_settings.lane_count;
        link->verified_link_cap.link_rate = link_settings.link_rate;

        dp_retrain_link_dp_test(link, &link_settings, false);
}

static void dp_test_get_audio_test_data(struct dc_link *link, bool disable_video)
{
        union audio_test_mode            dpcd_test_mode = {0};
        struct audio_test_pattern_type   dpcd_pattern_type = {0};
        union audio_test_pattern_period  dpcd_pattern_period[AUDIO_CHANNELS_COUNT] = {0};
        enum dp_test_pattern test_pattern = DP_TEST_PATTERN_AUDIO_OPERATOR_DEFINED;

        struct pipe_ctx *pipes = link->dc->current_state->res_ctx.pipe_ctx;
        struct pipe_ctx *pipe_ctx = &pipes[0];
        unsigned int channel_count;
        unsigned int channel = 0;
        unsigned int modes = 0;
        unsigned int sampling_rate_in_hz = 0;

        // get audio test mode and test pattern parameters
        core_link_read_dpcd(
                link,
                DP_TEST_AUDIO_MODE,
                &dpcd_test_mode.raw,
                sizeof(dpcd_test_mode));

        core_link_read_dpcd(
                link,
                DP_TEST_AUDIO_PATTERN_TYPE,
                &dpcd_pattern_type.value,
                sizeof(dpcd_pattern_type));

        channel_count = min(dpcd_test_mode.bits.channel_count + 1, AUDIO_CHANNELS_COUNT);

        // read pattern periods for requested channels when sawTooth pattern is requested
        if (dpcd_pattern_type.value == AUDIO_TEST_PATTERN_SAWTOOTH ||
                        dpcd_pattern_type.value == AUDIO_TEST_PATTERN_OPERATOR_DEFINED) {

                test_pattern = (dpcd_pattern_type.value == AUDIO_TEST_PATTERN_SAWTOOTH) ?
                                DP_TEST_PATTERN_AUDIO_SAWTOOTH : DP_TEST_PATTERN_AUDIO_OPERATOR_DEFINED;
                // read period for each channel
                for (channel = 0; channel < channel_count; channel++) {
                        core_link_read_dpcd(
                                                        link,
                                                        DP_TEST_AUDIO_PERIOD_CH1 + channel,
                                                        &dpcd_pattern_period[channel].raw,
                                                        sizeof(dpcd_pattern_period[channel]));
                }
        }

        // translate sampling rate
        switch (dpcd_test_mode.bits.sampling_rate) {
        case AUDIO_SAMPLING_RATE_32KHZ:
                sampling_rate_in_hz = 32000;
                break;
        case AUDIO_SAMPLING_RATE_44_1KHZ:
                sampling_rate_in_hz = 44100;
                break;
        case AUDIO_SAMPLING_RATE_48KHZ:
                sampling_rate_in_hz = 48000;
                break;
        case AUDIO_SAMPLING_RATE_88_2KHZ:
                sampling_rate_in_hz = 88200;
                break;
        case AUDIO_SAMPLING_RATE_96KHZ:
                sampling_rate_in_hz = 96000;
                break;
        case AUDIO_SAMPLING_RATE_176_4KHZ:
                sampling_rate_in_hz = 176400;
                break;
        case AUDIO_SAMPLING_RATE_192KHZ:
                sampling_rate_in_hz = 192000;
                break;
        default:
                sampling_rate_in_hz = 0;
                break;
        }

        link->audio_test_data.flags.test_requested = 1;
        link->audio_test_data.flags.disable_video = disable_video;
        link->audio_test_data.sampling_rate = sampling_rate_in_hz;
        link->audio_test_data.channel_count = channel_count;
        link->audio_test_data.pattern_type = test_pattern;

        if (test_pattern == DP_TEST_PATTERN_AUDIO_SAWTOOTH) {
                for (modes = 0; modes < pipe_ctx->stream->audio_info.mode_count; modes++) {
                        link->audio_test_data.pattern_period[modes] = dpcd_pattern_period[modes].bits.pattern_period;
                }
        }
}

/* TODO Raven hbr2 compliance eye output is unstable
 * (toggling on and off) with debugger break
 * This caueses intermittent PHY automation failure
 * Need to look into the root cause */
static void dp_test_send_phy_test_pattern(struct dc_link *link)
{
        union phy_test_pattern dpcd_test_pattern;
        union lane_adjust dpcd_lane_adjustment[2];
        unsigned char dpcd_post_cursor_2_adjustment = 0;
        unsigned char test_pattern_buffer[
                        (DP_TEST_264BIT_CUSTOM_PATTERN_263_256 -
                        DP_TEST_264BIT_CUSTOM_PATTERN_7_0)+1] = {0};
        unsigned int test_pattern_size = 0;
        enum dp_test_pattern test_pattern;
        union lane_adjust dpcd_lane_adjust;
        unsigned int lane;
        struct link_training_settings link_training_settings;
        unsigned char no_preshoot = 0;
        unsigned char no_deemphasis = 0;

        dpcd_test_pattern.raw = 0;
        memset(dpcd_lane_adjustment, 0, sizeof(dpcd_lane_adjustment));
        memset(&link_training_settings, 0, sizeof(link_training_settings));

        /* get phy test pattern and pattern parameters from DP receiver */
        core_link_read_dpcd(
                        link,
                        DP_PHY_TEST_PATTERN,
                        &dpcd_test_pattern.raw,
                        sizeof(dpcd_test_pattern));
        core_link_read_dpcd(
                        link,
                        DP_ADJUST_REQUEST_LANE0_1,
                        &dpcd_lane_adjustment[0].raw,
                        sizeof(dpcd_lane_adjustment));

        /* prepare link training settings */
        link_training_settings.link_settings = link->cur_link_settings;

        link_training_settings.lttpr_mode = dp_decide_lttpr_mode(link, &link->cur_link_settings);

        if (((link->chip_caps & AMD_EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK) == AMD_EXT_DISPLAY_PATH_CAPS__DP_FIXED_VS_EN) &&
                        link_training_settings.lttpr_mode == LTTPR_MODE_TRANSPARENT)
                dp_fixed_vs_pe_read_lane_adjust(
                                link,
                                link_training_settings.dpcd_lane_settings);

        /*get post cursor 2 parameters
         * For DP 1.1a or eariler, this DPCD register's value is 0
         * For DP 1.2 or later:
         * Bits 1:0 = POST_CURSOR2_LANE0; Bits 3:2 = POST_CURSOR2_LANE1
         * Bits 5:4 = POST_CURSOR2_LANE2; Bits 7:6 = POST_CURSOR2_LANE3
         */
        core_link_read_dpcd(
                        link,
                        DP_ADJUST_REQUEST_POST_CURSOR2,
                        &dpcd_post_cursor_2_adjustment,
                        sizeof(dpcd_post_cursor_2_adjustment));

        /* translate request */
        switch (dpcd_test_pattern.bits.PATTERN) {
        case PHY_TEST_PATTERN_D10_2:
                test_pattern = DP_TEST_PATTERN_D102;
                break;
        case PHY_TEST_PATTERN_SYMBOL_ERROR:
                test_pattern = DP_TEST_PATTERN_SYMBOL_ERROR;
                break;
        case PHY_TEST_PATTERN_PRBS7:
                test_pattern = DP_TEST_PATTERN_PRBS7;
                break;
        case PHY_TEST_PATTERN_80BIT_CUSTOM:
                test_pattern = DP_TEST_PATTERN_80BIT_CUSTOM;
                break;
        case PHY_TEST_PATTERN_CP2520_1:
                /* CP2520 pattern is unstable, temporarily use TPS4 instead */
                test_pattern = (link->dc->caps.force_dp_tps4_for_cp2520 == 1) ?
                                DP_TEST_PATTERN_TRAINING_PATTERN4 :
                                DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE;
                break;
        case PHY_TEST_PATTERN_CP2520_2:
                /* CP2520 pattern is unstable, temporarily use TPS4 instead */
                test_pattern = (link->dc->caps.force_dp_tps4_for_cp2520 == 1) ?
                                DP_TEST_PATTERN_TRAINING_PATTERN4 :
                                DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE;
                break;
        case PHY_TEST_PATTERN_CP2520_3:
                test_pattern = DP_TEST_PATTERN_TRAINING_PATTERN4;
                break;
        case PHY_TEST_PATTERN_128b_132b_TPS1:
                test_pattern = DP_TEST_PATTERN_128b_132b_TPS1;
                break;
        case PHY_TEST_PATTERN_128b_132b_TPS2:
                test_pattern = DP_TEST_PATTERN_128b_132b_TPS2;
                break;
        case PHY_TEST_PATTERN_PRBS9:
                test_pattern = DP_TEST_PATTERN_PRBS9;
                break;
        case PHY_TEST_PATTERN_PRBS11:
                test_pattern = DP_TEST_PATTERN_PRBS11;
                break;
        case PHY_TEST_PATTERN_PRBS15:
                test_pattern = DP_TEST_PATTERN_PRBS15;
                break;
        case PHY_TEST_PATTERN_PRBS23:
                test_pattern = DP_TEST_PATTERN_PRBS23;
                break;
        case PHY_TEST_PATTERN_PRBS31:
                test_pattern = DP_TEST_PATTERN_PRBS31;
                break;
        case PHY_TEST_PATTERN_264BIT_CUSTOM:
                test_pattern = DP_TEST_PATTERN_264BIT_CUSTOM;
                break;
        case PHY_TEST_PATTERN_SQUARE:
                test_pattern = DP_TEST_PATTERN_SQUARE;
                break;
        case PHY_TEST_PATTERN_SQUARE_PRESHOOT_DISABLED:
                test_pattern = DP_TEST_PATTERN_SQUARE_PRESHOOT_DISABLED;
                no_preshoot = 1;
                break;
        case PHY_TEST_PATTERN_SQUARE_DEEMPHASIS_DISABLED:
                test_pattern = DP_TEST_PATTERN_SQUARE_DEEMPHASIS_DISABLED;
                no_deemphasis = 1;
                break;
        case PHY_TEST_PATTERN_SQUARE_PRESHOOT_DEEMPHASIS_DISABLED:
                test_pattern = DP_TEST_PATTERN_SQUARE_PRESHOOT_DEEMPHASIS_DISABLED;
                no_preshoot = 1;
                no_deemphasis = 1;
                break;
        default:
                test_pattern = DP_TEST_PATTERN_VIDEO_MODE;
        break;
        }

        if (test_pattern == DP_TEST_PATTERN_80BIT_CUSTOM) {
                test_pattern_size = (DP_TEST_80BIT_CUSTOM_PATTERN_79_72 -
                                DP_TEST_80BIT_CUSTOM_PATTERN_7_0) + 1;
                core_link_read_dpcd(
                                link,
                                DP_TEST_80BIT_CUSTOM_PATTERN_7_0,
                                test_pattern_buffer,
                                test_pattern_size);
        }

        if (IS_DP_PHY_SQUARE_PATTERN(test_pattern)) {
                test_pattern_size = 1; // Square pattern data is 1 byte (DP spec)
                core_link_read_dpcd(
                                link,
                                DP_PHY_SQUARE_PATTERN,
                                test_pattern_buffer,
                                test_pattern_size);
        }

        if (test_pattern == DP_TEST_PATTERN_264BIT_CUSTOM) {
                test_pattern_size = (DP_TEST_264BIT_CUSTOM_PATTERN_263_256-
                                DP_TEST_264BIT_CUSTOM_PATTERN_7_0) + 1;
                core_link_read_dpcd(
                                link,
                                DP_TEST_264BIT_CUSTOM_PATTERN_7_0,
                                test_pattern_buffer,
                                test_pattern_size);
        }

        for (lane = 0; lane <
                (unsigned int)(link->cur_link_settings.lane_count);
                lane++) {
                dpcd_lane_adjust.raw =
                        dp_get_nibble_at_index(&dpcd_lane_adjustment[0].raw, lane);
                if (link_dp_get_encoding_format(&link->cur_link_settings) ==
                                DP_8b_10b_ENCODING) {
                        link_training_settings.hw_lane_settings[lane].VOLTAGE_SWING =
                                (enum dc_voltage_swing)
                                (dpcd_lane_adjust.bits.VOLTAGE_SWING_LANE);
                        link_training_settings.hw_lane_settings[lane].PRE_EMPHASIS =
                                (enum dc_pre_emphasis)
                                (dpcd_lane_adjust.bits.PRE_EMPHASIS_LANE);
                        link_training_settings.hw_lane_settings[lane].POST_CURSOR2 =
                                (enum dc_post_cursor2)
                                ((dpcd_post_cursor_2_adjustment >> (lane * 2)) & 0x03);
                } else if (link_dp_get_encoding_format(&link->cur_link_settings) ==
                                DP_128b_132b_ENCODING) {
                        link_training_settings.hw_lane_settings[lane].FFE_PRESET.settings.level =
                                        dpcd_lane_adjust.tx_ffe.PRESET_VALUE;
                        link_training_settings.hw_lane_settings[lane].FFE_PRESET.settings.no_preshoot = no_preshoot;
                        link_training_settings.hw_lane_settings[lane].FFE_PRESET.settings.no_deemphasis = no_deemphasis;
                }
        }

        dp_hw_to_dpcd_lane_settings(&link_training_settings,
                        link_training_settings.hw_lane_settings,
                        link_training_settings.dpcd_lane_settings);
        /*Usage: Measure DP physical lane signal
         * by DP SI test equipment automatically.
         * PHY test pattern request is generated by equipment via HPD interrupt.
         * HPD needs to be active all the time. HPD should be active
         * all the time. Do not touch it.
         * forward request to DS
         */
        dp_set_test_pattern(
                link,
                test_pattern,
                DP_TEST_PATTERN_COLOR_SPACE_UNDEFINED,
                &link_training_settings,
                test_pattern_buffer,
                test_pattern_size);
}

static void set_crtc_test_pattern(struct dc_link *link,
                                struct pipe_ctx *pipe_ctx,
                                enum dp_test_pattern test_pattern,
                                enum dp_test_pattern_color_space test_pattern_color_space)
{
        enum controller_dp_test_pattern controller_test_pattern;
        enum dc_color_depth color_depth = pipe_ctx->
                stream->timing.display_color_depth;
        struct bit_depth_reduction_params params;
        struct output_pixel_processor *opp = pipe_ctx->stream_res.opp;
        struct pipe_ctx *odm_pipe;
        struct test_pattern_params *tp_params;

        memset(&params, 0, sizeof(params));

        resource_build_test_pattern_params(&link->dc->current_state->res_ctx,
                        pipe_ctx);
        controller_test_pattern = pipe_ctx->stream_res.test_pattern_params.test_pattern;

        switch (test_pattern) {
        case DP_TEST_PATTERN_COLOR_SQUARES:
        case DP_TEST_PATTERN_COLOR_SQUARES_CEA:
        case DP_TEST_PATTERN_VERTICAL_BARS:
        case DP_TEST_PATTERN_HORIZONTAL_BARS:
        case DP_TEST_PATTERN_COLOR_RAMP:
        {
                /* disable bit depth reduction */
                pipe_ctx->stream->bit_depth_params = params;
                if (pipe_ctx->stream_res.tg->funcs->set_test_pattern) {
                        opp->funcs->opp_program_bit_depth_reduction(opp, &params);
                        pipe_ctx->stream_res.tg->funcs->set_test_pattern(pipe_ctx->stream_res.tg,
                                controller_test_pattern, color_depth);
                } else if (link->dc->hwss.set_disp_pattern_generator) {
                        enum controller_dp_color_space controller_color_space;
                        struct output_pixel_processor *odm_opp;

                        controller_color_space = pipe_ctx->stream_res.test_pattern_params.color_space;

                        if (controller_color_space == CONTROLLER_DP_COLOR_SPACE_UDEFINED) {
                                DC_LOG_ERROR("%s: Color space must be defined for test pattern", __func__);
                                ASSERT(0);
                        }

                        odm_pipe = pipe_ctx;
                        while (odm_pipe) {
                                tp_params = &odm_pipe->stream_res.test_pattern_params;
                                odm_opp = odm_pipe->stream_res.opp;
                                odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, &params);
                                link->dc->hwss.set_disp_pattern_generator(link->dc,
                                                odm_pipe,
                                                tp_params->test_pattern,
                                                tp_params->color_space,
                                                tp_params->color_depth,
                                                NULL,
                                                tp_params->width,
                                                tp_params->height,
                                                tp_params->offset);
                                odm_pipe = odm_pipe->next_odm_pipe;
                        }
                }
        }
        break;
        case DP_TEST_PATTERN_VIDEO_MODE:
        {
                /* restore bitdepth reduction */
                resource_build_bit_depth_reduction_params(pipe_ctx->stream, &params);
                pipe_ctx->stream->bit_depth_params = params;
                if (pipe_ctx->stream_res.tg->funcs->set_test_pattern) {
                        opp->funcs->opp_program_bit_depth_reduction(opp, &params);
                        pipe_ctx->stream_res.tg->funcs->set_test_pattern(pipe_ctx->stream_res.tg,
                                        CONTROLLER_DP_TEST_PATTERN_VIDEOMODE,
                                        color_depth);
                } else if (link->dc->hwss.set_disp_pattern_generator) {
                        struct output_pixel_processor *odm_opp;

                        odm_pipe = pipe_ctx;
                        while (odm_pipe) {
                                tp_params = &odm_pipe->stream_res.test_pattern_params;
                                odm_opp = odm_pipe->stream_res.opp;
                                odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, &params);
                                link->dc->hwss.set_disp_pattern_generator(link->dc,
                                                odm_pipe,
                                                tp_params->test_pattern,
                                                tp_params->color_space,
                                                tp_params->color_depth,
                                                NULL,
                                                tp_params->width,
                                                tp_params->height,
                                                tp_params->offset);
                                odm_pipe = odm_pipe->next_odm_pipe;
                        }
                }
        }
        break;

        default:
        break;
        }
}

void dp_handle_automated_test(struct dc_link *link)
{
        union test_request test_request;
        union test_response test_response;

        memset(&test_request, 0, sizeof(test_request));
        memset(&test_response, 0, sizeof(test_response));

        core_link_read_dpcd(
                link,
                DP_TEST_REQUEST,
                &test_request.raw,
                sizeof(union test_request));
        if (test_request.bits.LINK_TRAINING) {
                /* ACK first to let DP RX test box monitor LT sequence */
                test_response.bits.ACK = 1;
                core_link_write_dpcd(
                        link,
                        DP_TEST_RESPONSE,
                        &test_response.raw,
                        sizeof(test_response));
                dp_test_send_link_training(link);
                /* no acknowledge request is needed again */
                test_response.bits.ACK = 0;
        }
        if (test_request.bits.LINK_TEST_PATTRN) {
                union test_misc dpcd_test_params;
                union link_test_pattern dpcd_test_pattern;

                memset(&dpcd_test_pattern, 0, sizeof(dpcd_test_pattern));
                memset(&dpcd_test_params, 0, sizeof(dpcd_test_params));

                /* get link test pattern and pattern parameters */
                core_link_read_dpcd(
                                link,
                                DP_TEST_PATTERN,
                                &dpcd_test_pattern.raw,
                                sizeof(dpcd_test_pattern));
                core_link_read_dpcd(
                                link,
                                DP_TEST_MISC0,
                                &dpcd_test_params.raw,
                                sizeof(dpcd_test_params));
                test_response.bits.ACK = dm_helpers_dp_handle_test_pattern_request(link->ctx, link,
                                dpcd_test_pattern, dpcd_test_params) ? 1 : 0;
        }

        if (test_request.bits.AUDIO_TEST_PATTERN) {
                dp_test_get_audio_test_data(link, test_request.bits.TEST_AUDIO_DISABLED_VIDEO);
                test_response.bits.ACK = 1;
        }

        if (test_request.bits.PHY_TEST_PATTERN) {
                dp_test_send_phy_test_pattern(link);
                test_response.bits.ACK = 1;
        }

        /* send request acknowledgment */
        if (test_response.bits.ACK)
                core_link_write_dpcd(
                        link,
                        DP_TEST_RESPONSE,
                        &test_response.raw,
                        sizeof(test_response));
}

bool dp_set_test_pattern(
        struct dc_link *link,
        enum dp_test_pattern test_pattern,
        enum dp_test_pattern_color_space test_pattern_color_space,
        const struct link_training_settings *p_link_settings,
        const unsigned char *p_custom_pattern,
        unsigned int cust_pattern_size)
{
        const struct link_hwss *link_hwss;
        struct pipe_ctx *pipes = link->dc->current_state->res_ctx.pipe_ctx;
        struct pipe_ctx *pipe_ctx = NULL;
        unsigned int lane;
        unsigned int i;
        unsigned char link_qual_pattern[LANE_COUNT_DP_MAX] = {0};
        union dpcd_training_pattern training_pattern;
        enum dpcd_phy_test_patterns pattern;

        memset(&training_pattern, 0, sizeof(training_pattern));

        for (i = 0; i < MAX_PIPES; i++) {
                if (pipes[i].stream == NULL)
                        continue;

                if (resource_is_pipe_type(&pipes[i], OTG_MASTER) &&
                                pipes[i].stream->link == link) {
                        pipe_ctx = &pipes[i];
                        break;
                }
        }

        if (pipe_ctx == NULL)
                return false;

        link->pending_test_pattern = test_pattern;

        /* Reset CRTC Test Pattern if it is currently running and request is VideoMode */
        if (link->test_pattern_enabled && test_pattern ==
                        DP_TEST_PATTERN_VIDEO_MODE) {
                /* Set CRTC Test Pattern */
                set_crtc_test_pattern(link, pipe_ctx, test_pattern, test_pattern_color_space);
                dp_set_hw_test_pattern(link, &pipe_ctx->link_res, test_pattern,
                                (uint8_t *)p_custom_pattern,
                                (uint32_t)cust_pattern_size);

                /* Unblank Stream */
                link->dc->hwss.unblank_stream(
                        pipe_ctx,
                        &link->verified_link_cap);
                /* TODO:m_pHwss->MuteAudioEndpoint
                 * (pPathMode->pDisplayPath, false);
                 */

                /* Reset Test Pattern state */
                link->test_pattern_enabled = false;
                link->current_test_pattern = test_pattern;
                link->pending_test_pattern = DP_TEST_PATTERN_UNSUPPORTED;

                return true;
        }

        /* Check for PHY Test Patterns */
        if (IS_DP_PHY_PATTERN(test_pattern)) {
                /* Set DPCD Lane Settings before running test pattern */
                if (p_link_settings != NULL) {
                        if (((link->chip_caps & AMD_EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK) == AMD_EXT_DISPLAY_PATH_CAPS__DP_FIXED_VS_EN) &&
                                        p_link_settings->lttpr_mode == LTTPR_MODE_TRANSPARENT) {
                                dp_fixed_vs_pe_set_retimer_lane_settings(
                                                link,
                                                p_link_settings->dpcd_lane_settings,
                                                p_link_settings->link_settings.lane_count);
                        } else {
                                dp_set_hw_lane_settings(link, &pipe_ctx->link_res, p_link_settings, DPRX);
                        }
                        dpcd_set_lane_settings(link, p_link_settings, DPRX);
                }

                /* Blank stream if running test pattern */
                if (test_pattern != DP_TEST_PATTERN_VIDEO_MODE) {
                        /*TODO:
                         * m_pHwss->
                         * MuteAudioEndpoint(pPathMode->pDisplayPath, true);
                         */
                        /* Blank stream */
                        link->dc->hwss.blank_stream(pipe_ctx);
                }

                dp_set_hw_test_pattern(link, &pipe_ctx->link_res, test_pattern,
                                (uint8_t *)p_custom_pattern,
                                (uint32_t)cust_pattern_size);

                if (test_pattern != DP_TEST_PATTERN_VIDEO_MODE) {
                        /* Set Test Pattern state */
                        link->test_pattern_enabled = true;
                        link->current_test_pattern = test_pattern;
                        link->pending_test_pattern = DP_TEST_PATTERN_UNSUPPORTED;
                        if (p_link_settings != NULL)
                                dpcd_set_link_settings(link,
                                                p_link_settings);
                }

                switch (test_pattern) {
                case DP_TEST_PATTERN_VIDEO_MODE:
                        pattern = PHY_TEST_PATTERN_NONE;
                        break;
                case DP_TEST_PATTERN_D102:
                        pattern = PHY_TEST_PATTERN_D10_2;
                        break;
                case DP_TEST_PATTERN_SYMBOL_ERROR:
                        pattern = PHY_TEST_PATTERN_SYMBOL_ERROR;
                        break;
                case DP_TEST_PATTERN_PRBS7:
                        pattern = PHY_TEST_PATTERN_PRBS7;
                        break;
                case DP_TEST_PATTERN_80BIT_CUSTOM:
                        pattern = PHY_TEST_PATTERN_80BIT_CUSTOM;
                        break;
                case DP_TEST_PATTERN_CP2520_1:
                        pattern = PHY_TEST_PATTERN_CP2520_1;
                        break;
                case DP_TEST_PATTERN_CP2520_2:
                        pattern = PHY_TEST_PATTERN_CP2520_2;
                        break;
                case DP_TEST_PATTERN_CP2520_3:
                        pattern = PHY_TEST_PATTERN_CP2520_3;
                        break;
                case DP_TEST_PATTERN_128b_132b_TPS1:
                        pattern = PHY_TEST_PATTERN_128b_132b_TPS1;
                        break;
                case DP_TEST_PATTERN_128b_132b_TPS2:
                        pattern = PHY_TEST_PATTERN_128b_132b_TPS2;
                        break;
                case DP_TEST_PATTERN_PRBS9:
                        pattern = PHY_TEST_PATTERN_PRBS9;
                        break;
                case DP_TEST_PATTERN_PRBS11:
                        pattern = PHY_TEST_PATTERN_PRBS11;
                        break;
                case DP_TEST_PATTERN_PRBS15:
                        pattern = PHY_TEST_PATTERN_PRBS15;
                        break;
                case DP_TEST_PATTERN_PRBS23:
                        pattern = PHY_TEST_PATTERN_PRBS23;
                        break;
                case DP_TEST_PATTERN_PRBS31:
                        pattern = PHY_TEST_PATTERN_PRBS31;
                        break;
                case DP_TEST_PATTERN_264BIT_CUSTOM:
                        pattern = PHY_TEST_PATTERN_264BIT_CUSTOM;
                        break;
                case DP_TEST_PATTERN_SQUARE:
                        pattern = PHY_TEST_PATTERN_SQUARE;
                        break;
                case DP_TEST_PATTERN_SQUARE_PRESHOOT_DISABLED:
                        pattern = PHY_TEST_PATTERN_SQUARE_PRESHOOT_DISABLED;
                        break;
                case DP_TEST_PATTERN_SQUARE_DEEMPHASIS_DISABLED:
                        pattern = PHY_TEST_PATTERN_SQUARE_DEEMPHASIS_DISABLED;
                        break;
                case DP_TEST_PATTERN_SQUARE_PRESHOOT_DEEMPHASIS_DISABLED:
                        pattern = PHY_TEST_PATTERN_SQUARE_PRESHOOT_DEEMPHASIS_DISABLED;
                        break;
                default:
                        return false;
                }

                if (test_pattern == DP_TEST_PATTERN_VIDEO_MODE
                /*TODO:&& !pPathMode->pDisplayPath->IsTargetPoweredOn()*/)
                        return false;

                if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12) {
                        if (IS_DP_PHY_SQUARE_PATTERN(test_pattern))
                                core_link_write_dpcd(link,
                                                DP_LINK_SQUARE_PATTERN,
                                                p_custom_pattern,
                                                1);

                        /* tell receiver that we are sending qualification
                         * pattern DP 1.2 or later - DP receiver's link quality
                         * pattern is set using DPCD LINK_QUAL_LANEx_SET
                         * register (0x10B~0x10E)\
                         */
                        for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++)
                                link_qual_pattern[lane] =
                                                (unsigned char)(pattern);

                        core_link_write_dpcd(link,
                                        DP_LINK_QUAL_LANE0_SET,
                                        link_qual_pattern,
                                        sizeof(link_qual_pattern));
                } else if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_10 ||
                           link->dpcd_caps.dpcd_rev.raw == 0) {
                        /* tell receiver that we are sending qualification
                         * pattern DP 1.1a or earlier - DP receiver's link
                         * quality pattern is set using
                         * DPCD TRAINING_PATTERN_SET -> LINK_QUAL_PATTERN_SET
                         * register (0x102). We will use v_1.3 when we are
                         * setting test pattern for DP 1.1.
                         */
                        core_link_read_dpcd(link, DP_TRAINING_PATTERN_SET,
                                            &training_pattern.raw,
                                            sizeof(training_pattern));
                        training_pattern.v1_3.LINK_QUAL_PATTERN_SET = pattern;
                        core_link_write_dpcd(link, DP_TRAINING_PATTERN_SET,
                                             &training_pattern.raw,
                                             sizeof(training_pattern));
                }
        } else {
                enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;

                switch (test_pattern_color_space) {
                case DP_TEST_PATTERN_COLOR_SPACE_RGB:
                        color_space = COLOR_SPACE_SRGB;
                        if (test_pattern == DP_TEST_PATTERN_COLOR_SQUARES_CEA)
                                color_space = COLOR_SPACE_SRGB_LIMITED;
                        break;

                case DP_TEST_PATTERN_COLOR_SPACE_YCBCR601:
                        color_space = COLOR_SPACE_YCBCR601;
                        if (test_pattern == DP_TEST_PATTERN_COLOR_SQUARES_CEA)
                                color_space = COLOR_SPACE_YCBCR601_LIMITED;
                        break;
                case DP_TEST_PATTERN_COLOR_SPACE_YCBCR709:
                        color_space = COLOR_SPACE_YCBCR709;
                        if (test_pattern == DP_TEST_PATTERN_COLOR_SQUARES_CEA)
                                color_space = COLOR_SPACE_YCBCR709_LIMITED;
                        break;
                default:
                        break;
                }

                if (!pipe_ctx->stream)
                        return false;

                if (pipe_ctx->stream_res.tg->funcs->lock_doublebuffer_enable) {
                        if (should_use_dmub_inbox1_lock(pipe_ctx->stream->link->dc, pipe_ctx->stream->link)) {
                                union dmub_hw_lock_flags hw_locks = { 0 };
                                struct dmub_hw_lock_inst_flags inst_flags = { 0 };

                                hw_locks.bits.lock_dig = 1;
                                inst_flags.dig_inst = pipe_ctx->stream_res.tg->inst;

                                dmub_hw_lock_mgr_cmd(link->ctx->dmub_srv,
                                                        true,
                                                        &hw_locks,
                                                        &inst_flags);
                        } else
                                pipe_ctx->stream_res.tg->funcs->lock_doublebuffer_enable(
                                                pipe_ctx->stream_res.tg);
                }

                pipe_ctx->stream_res.tg->funcs->lock(pipe_ctx->stream_res.tg);
                /* update MSA to requested color space */
                link_hwss = get_link_hwss(link, &pipe_ctx->link_res);
                pipe_ctx->stream->output_color_space = color_space;
                link_hwss->setup_stream_attribute(pipe_ctx);

                if (pipe_ctx->stream->use_vsc_sdp_for_colorimetry) {
                        if (test_pattern == DP_TEST_PATTERN_COLOR_SQUARES_CEA)
                                pipe_ctx->stream->vsc_infopacket.sb[17] |= (1 << 7); // sb17 bit 7 Dynamic Range: 0 = VESA range, 1 = CTA range
                        else
                                pipe_ctx->stream->vsc_infopacket.sb[17] &= ~(1 << 7);

                        if (color_space == COLOR_SPACE_YCBCR601_LIMITED)
                                pipe_ctx->stream->vsc_infopacket.sb[16] &= 0xf0;
                        else if (color_space == COLOR_SPACE_YCBCR709_LIMITED)
                                pipe_ctx->stream->vsc_infopacket.sb[16] |= 1;

                        resource_build_info_frame(pipe_ctx);
                        link->dc->hwss.update_info_frame(pipe_ctx);
                }

                /* CRTC Patterns */
                set_crtc_test_pattern(link, pipe_ctx, test_pattern, test_pattern_color_space);
                pipe_ctx->stream_res.tg->funcs->unlock(pipe_ctx->stream_res.tg);
                pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg,
                                CRTC_STATE_VACTIVE);
                pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg,
                                CRTC_STATE_VBLANK);
                pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg,
                                CRTC_STATE_VACTIVE);

                if (pipe_ctx->stream_res.tg->funcs->lock_doublebuffer_disable) {
                        if (should_use_dmub_inbox1_lock(pipe_ctx->stream->link->dc, pipe_ctx->stream->link)) {
                                union dmub_hw_lock_flags hw_locks = { 0 };
                                struct dmub_hw_lock_inst_flags inst_flags = { 0 };

                                hw_locks.bits.lock_dig = 1;
                                inst_flags.dig_inst = pipe_ctx->stream_res.tg->inst;

                                dmub_hw_lock_mgr_cmd(link->ctx->dmub_srv,
                                                        false,
                                                        &hw_locks,
                                                        &inst_flags);
                        } else
                                pipe_ctx->stream_res.tg->funcs->lock_doublebuffer_disable(
                                                pipe_ctx->stream_res.tg);
                }

                /* Set Test Pattern state */
                link->test_pattern_enabled = true;
                link->current_test_pattern = test_pattern;
                link->pending_test_pattern = DP_TEST_PATTERN_UNSUPPORTED;
        }

        return true;
}

void dp_set_preferred_link_settings(struct dc *dc,
                struct dc_link_settings *link_setting,
                struct dc_link *link)
{
        int i;
        struct pipe_ctx *pipe;
        struct dc_stream_state *link_stream = 0;
        struct dc_link_settings store_settings = *link_setting;

        link->preferred_link_setting = store_settings;

        /* Retrain with preferred link settings only relevant for
         * DP signal type
         * Check for non-DP signal or if passive dongle present
         */
        if (!dc_is_dp_signal(link->connector_signal) ||
                link->dongle_max_pix_clk > 0)
                return;

        for (i = 0; i < MAX_PIPES; i++) {
                pipe = &dc->current_state->res_ctx.pipe_ctx[i];
                if (pipe->stream && pipe->stream->link) {
                        if (pipe->stream->link == link) {
                                link_stream = pipe->stream;
                                break;
                        }
                }
        }

        /* Stream not found */
        if (i == MAX_PIPES)
                return;

        /* Cannot retrain link if backend is off */
        if (link_stream->dpms_off)
                return;

        if (link_decide_link_settings(link_stream, &store_settings))
                dp_retrain_link_dp_test(link, &store_settings, false);
}

void dp_set_preferred_training_settings(struct dc *dc,
                struct dc_link_settings *link_setting,
                struct dc_link_training_overrides *lt_overrides,
                struct dc_link *link,
                bool skip_immediate_retrain)
{
        if (lt_overrides != NULL)
                link->preferred_training_settings = *lt_overrides;
        else
                memset(&link->preferred_training_settings, 0, sizeof(link->preferred_training_settings));

        if (link_setting != NULL) {
                link->preferred_link_setting = *link_setting;
        } else {
                link->preferred_link_setting.lane_count = LANE_COUNT_UNKNOWN;
                link->preferred_link_setting.link_rate = LINK_RATE_UNKNOWN;
        }

        if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT &&
                        link->type == dc_connection_mst_branch)
                dm_helpers_dp_mst_update_branch_bandwidth(dc->ctx, link);

        /* Retrain now, or wait until next stream update to apply */
        if (skip_immediate_retrain == false)
                dp_set_preferred_link_settings(dc, &link->preferred_link_setting, link);
}