/* * 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" #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: return LINK_RATE_UHBR13_5; default: return LINK_RATE_UNKNOWN; } } static bool is_dp_phy_sqaure_pattern(enum dp_test_pattern test_pattern) { return (DP_TEST_PATTERN_SQUARE_BEGIN <= test_pattern && test_pattern <= DP_TEST_PATTERN_SQUARE_END); } static bool is_dp_phy_pattern(enum dp_test_pattern test_pattern) { if ((DP_TEST_PATTERN_PHY_PATTERN_BEGIN <= test_pattern && test_pattern <= DP_TEST_PATTERN_PHY_PATTERN_END) || test_pattern == DP_TEST_PATTERN_VIDEO_MODE) return true; else return false; } 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; uint8_t count; int i; 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]); } for (i = count-1; i >= 0; i--) link_set_dpms_on(state, pipes[i]); } 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); /* 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 & 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_sqaure_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; int odm_cnt = 1; int h_active = pipe_ctx->stream->timing.h_addressable + pipe_ctx->stream->timing.h_border_left + pipe_ctx->stream->timing.h_border_right; int v_active = pipe_ctx->stream->timing.v_addressable + pipe_ctx->stream->timing.v_border_bottom + pipe_ctx->stream->timing.v_border_top; int odm_slice_width, last_odm_slice_width, offset = 0; memset(¶ms, 0, sizeof(params)); for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) odm_cnt++; odm_slice_width = h_active / odm_cnt; last_odm_slice_width = h_active - odm_slice_width * (odm_cnt - 1); switch (test_pattern) { case DP_TEST_PATTERN_COLOR_SQUARES: controller_test_pattern = CONTROLLER_DP_TEST_PATTERN_COLORSQUARES; break; case DP_TEST_PATTERN_COLOR_SQUARES_CEA: controller_test_pattern = CONTROLLER_DP_TEST_PATTERN_COLORSQUARES_CEA; break; case DP_TEST_PATTERN_VERTICAL_BARS: controller_test_pattern = CONTROLLER_DP_TEST_PATTERN_VERTICALBARS; break; case DP_TEST_PATTERN_HORIZONTAL_BARS: controller_test_pattern = CONTROLLER_DP_TEST_PATTERN_HORIZONTALBARS; break; case DP_TEST_PATTERN_COLOR_RAMP: controller_test_pattern = CONTROLLER_DP_TEST_PATTERN_COLORRAMP; break; default: controller_test_pattern = CONTROLLER_DP_TEST_PATTERN_VIDEOMODE; break; } 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, ¶ms); 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; switch (test_pattern_color_space) { case DP_TEST_PATTERN_COLOR_SPACE_RGB: controller_color_space = CONTROLLER_DP_COLOR_SPACE_RGB; break; case DP_TEST_PATTERN_COLOR_SPACE_YCBCR601: controller_color_space = CONTROLLER_DP_COLOR_SPACE_YCBCR601; break; case DP_TEST_PATTERN_COLOR_SPACE_YCBCR709: controller_color_space = CONTROLLER_DP_COLOR_SPACE_YCBCR709; break; case DP_TEST_PATTERN_COLOR_SPACE_UNDEFINED: default: controller_color_space = CONTROLLER_DP_COLOR_SPACE_UDEFINED; DC_LOG_ERROR("%s: Color space must be defined for test pattern", __func__); ASSERT(0); break; } odm_pipe = pipe_ctx; while (odm_pipe->next_odm_pipe) { odm_opp = odm_pipe->stream_res.opp; odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, ¶ms); link->dc->hwss.set_disp_pattern_generator(link->dc, odm_pipe, controller_test_pattern, controller_color_space, color_depth, NULL, odm_slice_width, v_active, offset); offset += odm_slice_width; odm_pipe = odm_pipe->next_odm_pipe; } odm_opp = odm_pipe->stream_res.opp; odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, ¶ms); link->dc->hwss.set_disp_pattern_generator(link->dc, odm_pipe, controller_test_pattern, controller_color_space, color_depth, NULL, last_odm_slice_width, v_active, offset); } } break; case DP_TEST_PATTERN_VIDEO_MODE: { /* restore bitdepth reduction */ resource_build_bit_depth_reduction_params(pipe_ctx->stream, ¶ms); 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, ¶ms); 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->next_odm_pipe) { odm_opp = odm_pipe->stream_res.opp; odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, ¶ms); link->dc->hwss.set_disp_pattern_generator(link->dc, odm_pipe, CONTROLLER_DP_TEST_PATTERN_VIDEOMODE, CONTROLLER_DP_COLOR_SPACE_UDEFINED, color_depth, NULL, odm_slice_width, v_active, offset); offset += odm_slice_width; odm_pipe = odm_pipe->next_odm_pipe; } odm_opp = odm_pipe->stream_res.opp; odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, ¶ms); link->dc->hwss.set_disp_pattern_generator(link->dc, odm_pipe, CONTROLLER_DP_TEST_PATTERN_VIDEOMODE, CONTROLLER_DP_COLOR_SPACE_UDEFINED, color_depth, NULL, last_odm_slice_width, v_active, offset); } } 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) { 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; /* 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; 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 & 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; 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_sqaure_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_res.tg->funcs->lock_doublebuffer_enable) { if (pipe_ctx->stream && should_use_dmub_lock(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 */ pipe_ctx->stream_res.stream_enc->funcs->dp_set_stream_attribute(pipe_ctx->stream_res.stream_enc, &pipe_ctx->stream->timing, color_space, pipe_ctx->stream->use_vsc_sdp_for_colorimetry, link->dpcd_caps.dprx_feature.bits.SST_SPLIT_SDP_CAP); 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); 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 (pipe_ctx->stream && should_use_dmub_lock(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; } 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; 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); }