| /* |
| * 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 retrieval and configuration of eDP panel features such |
| * as PSR and ABM and it also manages specs defined eDP panel power sequences. |
| */ |
| |
| #include "link_edp_panel_control.h" |
| #include "link_dpcd.h" |
| #include "link_dp_capability.h" |
| #include "dm_helpers.h" |
| #include "dal_asic_id.h" |
| #include "link_dp_phy.h" |
| #include "dce/dmub_psr.h" |
| #include "dc/dc_dmub_srv.h" |
| #include "dce/dmub_replay.h" |
| #include "abm.h" |
| #define DC_LOGGER \ |
| link->ctx->logger |
| #define DC_LOGGER_INIT(logger) |
| |
| #define DP_SINK_PR_ENABLE_AND_CONFIGURATION 0x37B |
| |
| /* Travis */ |
| static const uint8_t DP_VGA_LVDS_CONVERTER_ID_2[] = "sivarT"; |
| /* Nutmeg */ |
| static const uint8_t DP_VGA_LVDS_CONVERTER_ID_3[] = "dnomlA"; |
| |
| void dp_set_panel_mode(struct dc_link *link, enum dp_panel_mode panel_mode) |
| { |
| union dpcd_edp_config edp_config_set; |
| bool panel_mode_edp = false; |
| enum dc_status result; |
| |
| memset(&edp_config_set, '\0', sizeof(union dpcd_edp_config)); |
| |
| switch (panel_mode) { |
| case DP_PANEL_MODE_EDP: |
| case DP_PANEL_MODE_SPECIAL: |
| panel_mode_edp = true; |
| break; |
| |
| default: |
| break; |
| } |
| |
| /*set edp panel mode in receiver*/ |
| result = core_link_read_dpcd( |
| link, |
| DP_EDP_CONFIGURATION_SET, |
| &edp_config_set.raw, |
| sizeof(edp_config_set.raw)); |
| |
| if (result == DC_OK && |
| edp_config_set.bits.PANEL_MODE_EDP |
| != panel_mode_edp) { |
| |
| edp_config_set.bits.PANEL_MODE_EDP = |
| panel_mode_edp; |
| result = core_link_write_dpcd( |
| link, |
| DP_EDP_CONFIGURATION_SET, |
| &edp_config_set.raw, |
| sizeof(edp_config_set.raw)); |
| |
| ASSERT(result == DC_OK); |
| } |
| |
| link->panel_mode = panel_mode; |
| DC_LOG_DETECTION_DP_CAPS("Link: %d eDP panel mode supported: %d " |
| "eDP panel mode enabled: %d \n", |
| link->link_index, |
| link->dpcd_caps.panel_mode_edp, |
| panel_mode_edp); |
| } |
| |
| enum dp_panel_mode dp_get_panel_mode(struct dc_link *link) |
| { |
| /* We need to explicitly check that connector |
| * is not DP. Some Travis_VGA get reported |
| * by video bios as DP. |
| */ |
| if (link->connector_signal != SIGNAL_TYPE_DISPLAY_PORT) { |
| |
| switch (link->dpcd_caps.branch_dev_id) { |
| case DP_BRANCH_DEVICE_ID_0022B9: |
| /* alternate scrambler reset is required for Travis |
| * for the case when external chip does not |
| * provide sink device id, alternate scrambler |
| * scheme will be overriden later by querying |
| * Encoder features |
| */ |
| if (strncmp( |
| link->dpcd_caps.branch_dev_name, |
| DP_VGA_LVDS_CONVERTER_ID_2, |
| sizeof( |
| link->dpcd_caps. |
| branch_dev_name)) == 0) { |
| return DP_PANEL_MODE_SPECIAL; |
| } |
| break; |
| case DP_BRANCH_DEVICE_ID_00001A: |
| /* alternate scrambler reset is required for Travis |
| * for the case when external chip does not provide |
| * sink device id, alternate scrambler scheme will |
| * be overriden later by querying Encoder feature |
| */ |
| if (strncmp(link->dpcd_caps.branch_dev_name, |
| DP_VGA_LVDS_CONVERTER_ID_3, |
| sizeof( |
| link->dpcd_caps. |
| branch_dev_name)) == 0) { |
| return DP_PANEL_MODE_SPECIAL; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (link->dpcd_caps.panel_mode_edp && |
| (link->connector_signal == SIGNAL_TYPE_EDP || |
| (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT && |
| link->is_internal_display))) { |
| return DP_PANEL_MODE_EDP; |
| } |
| |
| return DP_PANEL_MODE_DEFAULT; |
| } |
| |
| bool edp_set_backlight_level_nits(struct dc_link *link, |
| bool isHDR, |
| uint32_t backlight_millinits, |
| uint32_t transition_time_in_ms) |
| { |
| struct dpcd_source_backlight_set dpcd_backlight_set; |
| uint8_t backlight_control = isHDR ? 1 : 0; |
| |
| if (!link || (link->connector_signal != SIGNAL_TYPE_EDP && |
| link->connector_signal != SIGNAL_TYPE_DISPLAY_PORT)) |
| return false; |
| |
| // OLEDs have no PWM, they can only use AUX |
| if (link->dpcd_sink_ext_caps.bits.oled == 1) |
| backlight_control = 1; |
| |
| *(uint32_t *)&dpcd_backlight_set.backlight_level_millinits = backlight_millinits; |
| *(uint16_t *)&dpcd_backlight_set.backlight_transition_time_ms = (uint16_t)transition_time_in_ms; |
| |
| |
| if (!link->dpcd_caps.panel_luminance_control) { |
| if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_LEVEL, |
| (uint8_t *)(&dpcd_backlight_set), |
| sizeof(dpcd_backlight_set)) != DC_OK) |
| return false; |
| |
| if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_CONTROL, |
| &backlight_control, 1) != DC_OK) |
| return false; |
| } else { |
| uint8_t backlight_enable = 0; |
| struct target_luminance_value *target_luminance = NULL; |
| |
| //if target luminance value is greater than 24 bits, clip the value to 24 bits |
| if (backlight_millinits > 0xFFFFFF) |
| backlight_millinits = 0xFFFFFF; |
| |
| target_luminance = (struct target_luminance_value *)&backlight_millinits; |
| |
| core_link_read_dpcd(link, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, |
| &backlight_enable, sizeof(uint8_t)); |
| |
| backlight_enable |= DP_EDP_PANEL_LUMINANCE_CONTROL_ENABLE; |
| |
| if (core_link_write_dpcd(link, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, |
| &backlight_enable, |
| sizeof(backlight_enable)) != DC_OK) |
| return false; |
| |
| if (core_link_write_dpcd(link, DP_EDP_PANEL_TARGET_LUMINANCE_VALUE, |
| (uint8_t *)(target_luminance), |
| sizeof(struct target_luminance_value)) != DC_OK) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool edp_get_backlight_level_nits(struct dc_link *link, |
| uint32_t *backlight_millinits_avg, |
| uint32_t *backlight_millinits_peak) |
| { |
| union dpcd_source_backlight_get dpcd_backlight_get; |
| |
| memset(&dpcd_backlight_get, 0, sizeof(union dpcd_source_backlight_get)); |
| |
| if (!link || (link->connector_signal != SIGNAL_TYPE_EDP && |
| link->connector_signal != SIGNAL_TYPE_DISPLAY_PORT)) |
| return false; |
| |
| if (!core_link_read_dpcd(link, DP_SOURCE_BACKLIGHT_CURRENT_PEAK, |
| dpcd_backlight_get.raw, |
| sizeof(union dpcd_source_backlight_get))) |
| return false; |
| |
| *backlight_millinits_avg = |
| dpcd_backlight_get.bytes.backlight_millinits_avg; |
| *backlight_millinits_peak = |
| dpcd_backlight_get.bytes.backlight_millinits_peak; |
| |
| /* On non-supported panels dpcd_read usually succeeds with 0 returned */ |
| if (*backlight_millinits_avg == 0 || |
| *backlight_millinits_avg > *backlight_millinits_peak) |
| return false; |
| |
| return true; |
| } |
| |
| bool edp_backlight_enable_aux(struct dc_link *link, bool enable) |
| { |
| uint8_t backlight_enable = enable ? 1 : 0; |
| |
| if (!link || (link->connector_signal != SIGNAL_TYPE_EDP && |
| link->connector_signal != SIGNAL_TYPE_DISPLAY_PORT)) |
| return false; |
| |
| if (core_link_write_dpcd(link, DP_SOURCE_BACKLIGHT_ENABLE, |
| &backlight_enable, 1) != DC_OK) |
| return false; |
| |
| return true; |
| } |
| |
| // we read default from 0x320 because we expect BIOS wrote it there |
| // regular get_backlight_nit reads from panel set at 0x326 |
| static bool read_default_bl_aux(struct dc_link *link, uint32_t *backlight_millinits) |
| { |
| if (!link || (link->connector_signal != SIGNAL_TYPE_EDP && |
| link->connector_signal != SIGNAL_TYPE_DISPLAY_PORT)) |
| return false; |
| |
| if (!link->dpcd_caps.panel_luminance_control) { |
| if (!core_link_read_dpcd(link, DP_SOURCE_BACKLIGHT_LEVEL, |
| (uint8_t *)backlight_millinits, |
| sizeof(uint32_t))) |
| return false; |
| } else { |
| //setting to 0 as a precaution, since target_luminance_value is 3 bytes |
| memset(backlight_millinits, 0, sizeof(uint32_t)); |
| |
| if (!core_link_read_dpcd(link, DP_EDP_PANEL_TARGET_LUMINANCE_VALUE, |
| (uint8_t *)backlight_millinits, |
| sizeof(struct target_luminance_value))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool set_default_brightness_aux(struct dc_link *link) |
| { |
| uint32_t default_backlight; |
| |
| if (link && link->dpcd_sink_ext_caps.bits.oled == 1) { |
| if (!read_default_bl_aux(link, &default_backlight)) |
| default_backlight = 150000; |
| // if > 5000, it might be wrong readback. 0 nits is a valid default value for OLED panel. |
| if (default_backlight < 1000 || default_backlight > 5000000) |
| default_backlight = 150000; |
| |
| return edp_set_backlight_level_nits(link, true, |
| default_backlight, 0); |
| } |
| return false; |
| } |
| |
| bool edp_is_ilr_optimization_enabled(struct dc_link *link) |
| { |
| if (link->dpcd_caps.edp_supported_link_rates_count == 0 || !link->panel_config.ilr.optimize_edp_link_rate) |
| return false; |
| return true; |
| } |
| |
| enum dc_link_rate get_max_link_rate_from_ilr_table(struct dc_link *link) |
| { |
| enum dc_link_rate link_rate = link->reported_link_cap.link_rate; |
| |
| for (int i = 0; i < link->dpcd_caps.edp_supported_link_rates_count; i++) { |
| if (link_rate < link->dpcd_caps.edp_supported_link_rates[i]) |
| link_rate = link->dpcd_caps.edp_supported_link_rates[i]; |
| } |
| |
| return link_rate; |
| } |
| |
| bool edp_is_ilr_optimization_required(struct dc_link *link, |
| struct dc_crtc_timing *crtc_timing) |
| { |
| struct dc_link_settings link_setting; |
| uint8_t link_bw_set; |
| uint8_t link_rate_set; |
| uint32_t req_bw; |
| union lane_count_set lane_count_set = {0}; |
| |
| ASSERT(link || crtc_timing); // invalid input |
| |
| if (!edp_is_ilr_optimization_enabled(link)) |
| return false; |
| |
| |
| // Read DPCD 00100h to find if standard link rates are set |
| core_link_read_dpcd(link, DP_LINK_BW_SET, |
| &link_bw_set, sizeof(link_bw_set)); |
| |
| if (link_bw_set) { |
| DC_LOG_EVENT_LINK_TRAINING("eDP ILR: Optimization required, VBIOS used link_bw_set\n"); |
| return true; |
| } |
| |
| // Read DPCD 00115h to find the edp link rate set used |
| core_link_read_dpcd(link, DP_LINK_RATE_SET, |
| &link_rate_set, sizeof(link_rate_set)); |
| |
| // Read DPCD 00101h to find out the number of lanes currently set |
| core_link_read_dpcd(link, DP_LANE_COUNT_SET, |
| &lane_count_set.raw, sizeof(lane_count_set)); |
| |
| req_bw = dc_bandwidth_in_kbps_from_timing(crtc_timing, dc_link_get_highest_encoding_format(link)); |
| |
| if (!crtc_timing->flags.DSC) |
| edp_decide_link_settings(link, &link_setting, req_bw); |
| else |
| decide_edp_link_settings_with_dsc(link, &link_setting, req_bw, LINK_RATE_UNKNOWN); |
| |
| if (link->dpcd_caps.edp_supported_link_rates[link_rate_set] != link_setting.link_rate || |
| lane_count_set.bits.LANE_COUNT_SET != link_setting.lane_count) { |
| DC_LOG_EVENT_LINK_TRAINING("eDP ILR: Optimization required, VBIOS link_rate_set not optimal\n"); |
| return true; |
| } |
| |
| DC_LOG_EVENT_LINK_TRAINING("eDP ILR: No optimization required, VBIOS set optimal link_rate_set\n"); |
| return false; |
| } |
| |
| void edp_panel_backlight_power_on(struct dc_link *link, bool wait_for_hpd) |
| { |
| if (link->connector_signal != SIGNAL_TYPE_EDP) |
| return; |
| |
| link->dc->hwss.edp_power_control(link, true); |
| if (wait_for_hpd) |
| link->dc->hwss.edp_wait_for_hpd_ready(link, true); |
| if (link->dc->hwss.edp_backlight_control) |
| link->dc->hwss.edp_backlight_control(link, true); |
| } |
| |
| void edp_set_panel_power(struct dc_link *link, bool powerOn) |
| { |
| if (powerOn) { |
| // 1. panel VDD on |
| if (!link->dc->config.edp_no_power_sequencing) |
| link->dc->hwss.edp_power_control(link, true); |
| link->dc->hwss.edp_wait_for_hpd_ready(link, true); |
| |
| // 2. panel BL on |
| if (link->dc->hwss.edp_backlight_control) |
| link->dc->hwss.edp_backlight_control(link, true); |
| |
| // 3. Rx power on |
| dpcd_write_rx_power_ctrl(link, true); |
| } else { |
| // 3. Rx power off |
| dpcd_write_rx_power_ctrl(link, false); |
| |
| // 2. panel BL off |
| if (link->dc->hwss.edp_backlight_control) |
| link->dc->hwss.edp_backlight_control(link, false); |
| |
| // 1. panel VDD off |
| if (!link->dc->config.edp_no_power_sequencing) |
| link->dc->hwss.edp_power_control(link, false); |
| } |
| } |
| |
| bool edp_wait_for_t12(struct dc_link *link) |
| { |
| if (link->connector_signal == SIGNAL_TYPE_EDP && link->dc->hwss.edp_wait_for_T12) { |
| link->dc->hwss.edp_wait_for_T12(link); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void edp_add_delay_for_T9(struct dc_link *link) |
| { |
| if (link && link->panel_config.pps.extra_delay_backlight_off > 0) |
| fsleep(link->panel_config.pps.extra_delay_backlight_off * 1000); |
| } |
| |
| bool edp_receiver_ready_T9(struct dc_link *link) |
| { |
| unsigned int tries = 0; |
| unsigned char sinkstatus = 0; |
| unsigned char edpRev = 0; |
| enum dc_status result = DC_OK; |
| |
| result = core_link_read_dpcd(link, DP_EDP_DPCD_REV, &edpRev, sizeof(edpRev)); |
| |
| /* start from eDP version 1.2, SINK_STAUS indicate the sink is ready.*/ |
| if (result == DC_OK && edpRev >= DP_EDP_12) { |
| do { |
| sinkstatus = 1; |
| result = core_link_read_dpcd(link, DP_SINK_STATUS, &sinkstatus, sizeof(sinkstatus)); |
| if (sinkstatus == 0) |
| break; |
| if (result != DC_OK) |
| break; |
| udelay(100); //MAx T9 |
| } while (++tries < 50); |
| } |
| |
| return result; |
| } |
| |
| bool edp_receiver_ready_T7(struct dc_link *link) |
| { |
| unsigned char sinkstatus = 0; |
| unsigned char edpRev = 0; |
| enum dc_status result = DC_OK; |
| |
| /* use absolute time stamp to constrain max T7*/ |
| unsigned long long enter_timestamp = 0; |
| unsigned long long finish_timestamp = 0; |
| unsigned long long time_taken_in_ns = 0; |
| |
| result = core_link_read_dpcd(link, DP_EDP_DPCD_REV, &edpRev, sizeof(edpRev)); |
| |
| if (result == DC_OK && edpRev >= DP_EDP_12) { |
| /* start from eDP version 1.2, SINK_STAUS indicate the sink is ready.*/ |
| enter_timestamp = dm_get_timestamp(link->ctx); |
| do { |
| sinkstatus = 0; |
| result = core_link_read_dpcd(link, DP_SINK_STATUS, &sinkstatus, sizeof(sinkstatus)); |
| if (sinkstatus == 1) |
| break; |
| if (result != DC_OK) |
| break; |
| udelay(25); |
| finish_timestamp = dm_get_timestamp(link->ctx); |
| time_taken_in_ns = dm_get_elapse_time_in_ns(link->ctx, finish_timestamp, enter_timestamp); |
| } while (time_taken_in_ns < 50 * 1000000); //MAx T7 is 50ms |
| } |
| |
| if (link && link->panel_config.pps.extra_t7_ms > 0) |
| fsleep(link->panel_config.pps.extra_t7_ms * 1000); |
| |
| return result; |
| } |
| |
| bool edp_power_alpm_dpcd_enable(struct dc_link *link, bool enable) |
| { |
| bool ret = false; |
| union dpcd_alpm_configuration alpm_config; |
| |
| if (link->psr_settings.psr_version == DC_PSR_VERSION_SU_1) { |
| memset(&alpm_config, 0, sizeof(alpm_config)); |
| |
| alpm_config.bits.ENABLE = (enable ? true : false); |
| ret = dm_helpers_dp_write_dpcd(link->ctx, link, |
| DP_RECEIVER_ALPM_CONFIG, &alpm_config.raw, |
| sizeof(alpm_config.raw)); |
| } |
| return ret; |
| } |
| |
| static struct pipe_ctx *get_pipe_from_link(const struct dc_link *link) |
| { |
| int i; |
| struct dc *dc = link->ctx->dc; |
| struct pipe_ctx *pipe_ctx = NULL; |
| |
| for (i = 0; i < MAX_PIPES; i++) { |
| if (dc->current_state->res_ctx.pipe_ctx[i].stream) { |
| if (dc->current_state->res_ctx.pipe_ctx[i].stream->link == link) { |
| pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; |
| break; |
| } |
| } |
| } |
| |
| return pipe_ctx; |
| } |
| |
| bool edp_set_backlight_level(const struct dc_link *link, |
| uint32_t backlight_pwm_u16_16, |
| uint32_t frame_ramp) |
| { |
| struct dc *dc = link->ctx->dc; |
| |
| DC_LOGGER_INIT(link->ctx->logger); |
| DC_LOG_BACKLIGHT("New Backlight level: %d (0x%X)\n", |
| backlight_pwm_u16_16, backlight_pwm_u16_16); |
| |
| if (dc_is_embedded_signal(link->connector_signal)) { |
| struct pipe_ctx *pipe_ctx = get_pipe_from_link(link); |
| |
| if (link->panel_cntl) |
| link->panel_cntl->stored_backlight_registers.USER_LEVEL = backlight_pwm_u16_16; |
| |
| if (pipe_ctx) { |
| /* Disable brightness ramping when the display is blanked |
| * as it can hang the DMCU |
| */ |
| if (pipe_ctx->plane_state == NULL) |
| frame_ramp = 0; |
| } else { |
| return false; |
| } |
| |
| dc->hwss.set_backlight_level( |
| pipe_ctx, |
| backlight_pwm_u16_16, |
| frame_ramp); |
| } |
| return true; |
| } |
| |
| bool edp_set_psr_allow_active(struct dc_link *link, const bool *allow_active, |
| bool wait, bool force_static, const unsigned int *power_opts) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmcu *dmcu = dc->res_pool->dmcu; |
| struct dmub_psr *psr = dc->res_pool->psr; |
| unsigned int panel_inst; |
| |
| if (psr == NULL && force_static) |
| return false; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| if ((allow_active != NULL) && (*allow_active == true) && (link->type == dc_connection_none)) { |
| // Don't enter PSR if panel is not connected |
| return false; |
| } |
| |
| /* Set power optimization flag */ |
| if (power_opts && link->psr_settings.psr_power_opt != *power_opts) { |
| link->psr_settings.psr_power_opt = *power_opts; |
| |
| if (psr != NULL && link->psr_settings.psr_feature_enabled && psr->funcs->psr_set_power_opt) |
| psr->funcs->psr_set_power_opt(psr, link->psr_settings.psr_power_opt, panel_inst); |
| } |
| |
| if (psr != NULL && link->psr_settings.psr_feature_enabled && |
| force_static && psr->funcs->psr_force_static) |
| psr->funcs->psr_force_static(psr, panel_inst); |
| |
| /* Enable or Disable PSR */ |
| if (allow_active && link->psr_settings.psr_allow_active != *allow_active) { |
| link->psr_settings.psr_allow_active = *allow_active; |
| |
| if (!link->psr_settings.psr_allow_active) |
| dc_z10_restore(dc); |
| |
| if (psr != NULL && link->psr_settings.psr_feature_enabled) { |
| psr->funcs->psr_enable(psr, link->psr_settings.psr_allow_active, wait, panel_inst); |
| } else if ((dmcu != NULL && dmcu->funcs->is_dmcu_initialized(dmcu)) && |
| link->psr_settings.psr_feature_enabled) |
| dmcu->funcs->set_psr_enable(dmcu, link->psr_settings.psr_allow_active, wait); |
| else |
| return false; |
| } |
| return true; |
| } |
| |
| bool edp_get_psr_state(const struct dc_link *link, enum dc_psr_state *state) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmcu *dmcu = dc->res_pool->dmcu; |
| struct dmub_psr *psr = dc->res_pool->psr; |
| unsigned int panel_inst; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| if (psr != NULL && link->psr_settings.psr_feature_enabled) |
| psr->funcs->psr_get_state(psr, state, panel_inst); |
| else if (dmcu != NULL && link->psr_settings.psr_feature_enabled) |
| dmcu->funcs->get_psr_state(dmcu, state); |
| |
| return true; |
| } |
| |
| static inline enum physical_phy_id |
| transmitter_to_phy_id(struct dc_link *link) |
| { |
| struct dc_context *dc_ctx = link->ctx; |
| enum transmitter transmitter_value = link->link_enc->transmitter; |
| |
| switch (transmitter_value) { |
| case TRANSMITTER_UNIPHY_A: |
| return PHYLD_0; |
| case TRANSMITTER_UNIPHY_B: |
| return PHYLD_1; |
| case TRANSMITTER_UNIPHY_C: |
| return PHYLD_2; |
| case TRANSMITTER_UNIPHY_D: |
| return PHYLD_3; |
| case TRANSMITTER_UNIPHY_E: |
| return PHYLD_4; |
| case TRANSMITTER_UNIPHY_F: |
| return PHYLD_5; |
| case TRANSMITTER_NUTMEG_CRT: |
| return PHYLD_6; |
| case TRANSMITTER_TRAVIS_CRT: |
| return PHYLD_7; |
| case TRANSMITTER_TRAVIS_LCD: |
| return PHYLD_8; |
| case TRANSMITTER_UNIPHY_G: |
| return PHYLD_9; |
| case TRANSMITTER_COUNT: |
| return PHYLD_COUNT; |
| case TRANSMITTER_UNKNOWN: |
| return PHYLD_UNKNOWN; |
| default: |
| DC_ERROR("Unknown transmitter value %d\n", transmitter_value); |
| return PHYLD_UNKNOWN; |
| } |
| } |
| |
| bool edp_setup_psr(struct dc_link *link, |
| const struct dc_stream_state *stream, struct psr_config *psr_config, |
| struct psr_context *psr_context) |
| { |
| struct dc *dc; |
| struct dmcu *dmcu; |
| struct dmub_psr *psr; |
| int i; |
| unsigned int panel_inst; |
| /* updateSinkPsrDpcdConfig*/ |
| union dpcd_psr_configuration psr_configuration; |
| union dpcd_sink_active_vtotal_control_mode vtotal_control = {0}; |
| |
| psr_context->controllerId = CONTROLLER_ID_UNDEFINED; |
| |
| if (!link) |
| return false; |
| |
| dc = link->ctx->dc; |
| dmcu = dc->res_pool->dmcu; |
| psr = dc->res_pool->psr; |
| |
| if (!dmcu && !psr) |
| return false; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| |
| memset(&psr_configuration, 0, sizeof(psr_configuration)); |
| |
| psr_configuration.bits.ENABLE = 1; |
| psr_configuration.bits.CRC_VERIFICATION = 1; |
| psr_configuration.bits.FRAME_CAPTURE_INDICATION = |
| psr_config->psr_frame_capture_indication_req; |
| |
| /* Check for PSR v2*/ |
| if (link->psr_settings.psr_version == DC_PSR_VERSION_SU_1) { |
| /* For PSR v2 selective update. |
| * Indicates whether sink should start capturing |
| * immediately following active scan line, |
| * or starting with the 2nd active scan line. |
| */ |
| psr_configuration.bits.LINE_CAPTURE_INDICATION = 0; |
| /*For PSR v2, determines whether Sink should generate |
| * IRQ_HPD when CRC mismatch is detected. |
| */ |
| psr_configuration.bits.IRQ_HPD_WITH_CRC_ERROR = 1; |
| /* For PSR v2, set the bit when the Source device will |
| * be enabling PSR2 operation. |
| */ |
| psr_configuration.bits.ENABLE_PSR2 = 1; |
| /* For PSR v2, the Sink device must be able to receive |
| * SU region updates early in the frame time. |
| */ |
| psr_configuration.bits.EARLY_TRANSPORT_ENABLE = 1; |
| } |
| |
| dm_helpers_dp_write_dpcd( |
| link->ctx, |
| link, |
| 368, |
| &psr_configuration.raw, |
| sizeof(psr_configuration.raw)); |
| |
| if (link->psr_settings.psr_version == DC_PSR_VERSION_SU_1) { |
| edp_power_alpm_dpcd_enable(link, true); |
| psr_context->su_granularity_required = |
| psr_config->su_granularity_required; |
| psr_context->su_y_granularity = |
| psr_config->su_y_granularity; |
| psr_context->line_time_in_us = psr_config->line_time_in_us; |
| |
| /* linux must be able to expose AMD Source DPCD definition |
| * in order to support FreeSync PSR |
| */ |
| if (link->psr_settings.psr_vtotal_control_support) { |
| psr_context->rate_control_caps = psr_config->rate_control_caps; |
| vtotal_control.bits.ENABLE = true; |
| core_link_write_dpcd(link, DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_MODE, |
| &vtotal_control.raw, sizeof(vtotal_control.raw)); |
| } |
| } |
| |
| psr_context->channel = link->ddc->ddc_pin->hw_info.ddc_channel; |
| psr_context->transmitterId = link->link_enc->transmitter; |
| psr_context->engineId = link->link_enc->preferred_engine; |
| |
| for (i = 0; i < MAX_PIPES; i++) { |
| if (dc->current_state->res_ctx.pipe_ctx[i].stream |
| == stream) { |
| /* dmcu -1 for all controller id values, |
| * therefore +1 here |
| */ |
| psr_context->controllerId = |
| dc->current_state->res_ctx. |
| pipe_ctx[i].stream_res.tg->inst + 1; |
| break; |
| } |
| } |
| |
| /* Hardcoded for now. Can be Pcie or Uniphy (or Unknown)*/ |
| psr_context->phyType = PHY_TYPE_UNIPHY; |
| /*PhyId is associated with the transmitter id*/ |
| psr_context->smuPhyId = transmitter_to_phy_id(link); |
| |
| psr_context->crtcTimingVerticalTotal = stream->timing.v_total; |
| psr_context->vsync_rate_hz = div64_u64(div64_u64((stream-> |
| timing.pix_clk_100hz * 100), |
| stream->timing.v_total), |
| stream->timing.h_total); |
| |
| psr_context->psrSupportedDisplayConfig = true; |
| psr_context->psrExitLinkTrainingRequired = |
| psr_config->psr_exit_link_training_required; |
| psr_context->sdpTransmitLineNumDeadline = |
| psr_config->psr_sdp_transmit_line_num_deadline; |
| psr_context->psrFrameCaptureIndicationReq = |
| psr_config->psr_frame_capture_indication_req; |
| |
| psr_context->skipPsrWaitForPllLock = 0; /* only = 1 in KV */ |
| |
| psr_context->numberOfControllers = |
| link->dc->res_pool->timing_generator_count; |
| |
| psr_context->rfb_update_auto_en = true; |
| |
| /* 2 frames before enter PSR. */ |
| psr_context->timehyst_frames = 2; |
| /* half a frame |
| * (units in 100 lines, i.e. a value of 1 represents 100 lines) |
| */ |
| psr_context->hyst_lines = stream->timing.v_total / 2 / 100; |
| psr_context->aux_repeats = 10; |
| |
| psr_context->psr_level.u32all = 0; |
| |
| /*skip power down the single pipe since it blocks the cstate*/ |
| if (link->ctx->asic_id.chip_family >= FAMILY_RV) { |
| switch (link->ctx->asic_id.chip_family) { |
| case FAMILY_YELLOW_CARP: |
| case AMDGPU_FAMILY_GC_10_3_6: |
| case AMDGPU_FAMILY_GC_11_0_1: |
| if (dc->debug.disable_z10 || dc->debug.psr_skip_crtc_disable) |
| psr_context->psr_level.bits.SKIP_CRTC_DISABLE = true; |
| break; |
| default: |
| psr_context->psr_level.bits.SKIP_CRTC_DISABLE = true; |
| break; |
| } |
| } |
| |
| /* SMU will perform additional powerdown sequence. |
| * For unsupported ASICs, set psr_level flag to skip PSR |
| * static screen notification to SMU. |
| * (Always set for DAL2, did not check ASIC) |
| */ |
| psr_context->allow_smu_optimizations = psr_config->allow_smu_optimizations; |
| psr_context->allow_multi_disp_optimizations = psr_config->allow_multi_disp_optimizations; |
| |
| /* Complete PSR entry before aborting to prevent intermittent |
| * freezes on certain eDPs |
| */ |
| psr_context->psr_level.bits.DISABLE_PSR_ENTRY_ABORT = 1; |
| |
| /* Disable ALPM first for compatible non-ALPM panel now */ |
| psr_context->psr_level.bits.DISABLE_ALPM = 0; |
| psr_context->psr_level.bits.ALPM_DEFAULT_PD_MODE = 1; |
| |
| /* Controls additional delay after remote frame capture before |
| * continuing power down, default = 0 |
| */ |
| psr_context->frame_delay = 0; |
| |
| psr_context->dsc_slice_height = psr_config->dsc_slice_height; |
| |
| if (psr) { |
| link->psr_settings.psr_feature_enabled = psr->funcs->psr_copy_settings(psr, |
| link, psr_context, panel_inst); |
| link->psr_settings.psr_power_opt = 0; |
| link->psr_settings.psr_allow_active = 0; |
| } else { |
| link->psr_settings.psr_feature_enabled = dmcu->funcs->setup_psr(dmcu, link, psr_context); |
| } |
| |
| /* psr_enabled == 0 indicates setup_psr did not succeed, but this |
| * should not happen since firmware should be running at this point |
| */ |
| if (link->psr_settings.psr_feature_enabled == 0) |
| ASSERT(0); |
| |
| return true; |
| |
| } |
| |
| void edp_get_psr_residency(const struct dc_link *link, uint32_t *residency) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_psr *psr = dc->res_pool->psr; |
| unsigned int panel_inst; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return; |
| |
| // PSR residency measurements only supported on DMCUB |
| if (psr != NULL && link->psr_settings.psr_feature_enabled) |
| psr->funcs->psr_get_residency(psr, residency, panel_inst); |
| else |
| *residency = 0; |
| } |
| bool edp_set_sink_vtotal_in_psr_active(const struct dc_link *link, uint16_t psr_vtotal_idle, uint16_t psr_vtotal_su) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_psr *psr = dc->res_pool->psr; |
| |
| if (psr == NULL || !link->psr_settings.psr_feature_enabled || !link->psr_settings.psr_vtotal_control_support) |
| return false; |
| |
| psr->funcs->psr_set_sink_vtotal_in_psr_active(psr, psr_vtotal_idle, psr_vtotal_su); |
| |
| return true; |
| } |
| |
| bool edp_set_replay_allow_active(struct dc_link *link, const bool *allow_active, |
| bool wait, bool force_static, const unsigned int *power_opts) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_replay *replay = dc->res_pool->replay; |
| unsigned int panel_inst; |
| |
| if (replay == NULL && force_static) |
| return false; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| /* Set power optimization flag */ |
| if (power_opts && link->replay_settings.replay_power_opt_active != *power_opts) { |
| if (replay != NULL && link->replay_settings.replay_feature_enabled && |
| replay->funcs->replay_set_power_opt) { |
| replay->funcs->replay_set_power_opt(replay, *power_opts, panel_inst); |
| link->replay_settings.replay_power_opt_active = *power_opts; |
| } |
| } |
| |
| /* Activate or deactivate Replay */ |
| if (allow_active && link->replay_settings.replay_allow_active != *allow_active) { |
| // TODO: Handle mux change case if force_static is set |
| // If force_static is set, just change the replay_allow_active state directly |
| if (replay != NULL && link->replay_settings.replay_feature_enabled) |
| replay->funcs->replay_enable(replay, *allow_active, wait, panel_inst); |
| link->replay_settings.replay_allow_active = *allow_active; |
| } |
| |
| return true; |
| } |
| |
| bool edp_get_replay_state(const struct dc_link *link, uint64_t *state) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_replay *replay = dc->res_pool->replay; |
| unsigned int panel_inst; |
| enum replay_state pr_state = REPLAY_STATE_0; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| if (replay != NULL && link->replay_settings.replay_feature_enabled) |
| replay->funcs->replay_get_state(replay, &pr_state, panel_inst); |
| *state = pr_state; |
| |
| return true; |
| } |
| |
| bool edp_setup_replay(struct dc_link *link, const struct dc_stream_state *stream) |
| { |
| /* To-do: Setup Replay */ |
| struct dc *dc; |
| struct dmub_replay *replay; |
| int i; |
| unsigned int panel_inst; |
| struct replay_context replay_context = { 0 }; |
| unsigned int lineTimeInNs = 0; |
| |
| |
| union replay_enable_and_configuration replay_config; |
| |
| union dpcd_alpm_configuration alpm_config; |
| |
| replay_context.controllerId = CONTROLLER_ID_UNDEFINED; |
| |
| if (!link) |
| return false; |
| |
| dc = link->ctx->dc; |
| |
| replay = dc->res_pool->replay; |
| |
| if (!replay) |
| return false; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel; |
| replay_context.digbe_inst = link->link_enc->transmitter; |
| replay_context.digfe_inst = link->link_enc->preferred_engine; |
| |
| for (i = 0; i < MAX_PIPES; i++) { |
| if (dc->current_state->res_ctx.pipe_ctx[i].stream |
| == stream) { |
| /* dmcu -1 for all controller id values, |
| * therefore +1 here |
| */ |
| replay_context.controllerId = |
| dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg->inst + 1; |
| break; |
| } |
| } |
| |
| lineTimeInNs = |
| ((stream->timing.h_total * 1000000) / |
| (stream->timing.pix_clk_100hz / 10)) + 1; |
| |
| replay_context.line_time_in_ns = lineTimeInNs; |
| |
| link->replay_settings.replay_feature_enabled = |
| replay->funcs->replay_copy_settings(replay, link, &replay_context, panel_inst); |
| if (link->replay_settings.replay_feature_enabled) { |
| |
| replay_config.bits.FREESYNC_PANEL_REPLAY_MODE = 1; |
| replay_config.bits.TIMING_DESYNC_ERROR_VERIFICATION = |
| link->replay_settings.config.replay_timing_sync_supported; |
| replay_config.bits.STATE_TRANSITION_ERROR_DETECTION = 1; |
| dm_helpers_dp_write_dpcd(link->ctx, link, |
| DP_SINK_PR_ENABLE_AND_CONFIGURATION, |
| (uint8_t *)&(replay_config.raw), sizeof(uint8_t)); |
| |
| memset(&alpm_config, 0, sizeof(alpm_config)); |
| alpm_config.bits.ENABLE = 1; |
| dm_helpers_dp_write_dpcd( |
| link->ctx, |
| link, |
| DP_RECEIVER_ALPM_CONFIG, |
| &alpm_config.raw, |
| sizeof(alpm_config.raw)); |
| } |
| return true; |
| } |
| |
| /* |
| * This is general Interface for Replay to set an 32 bit variable to dmub |
| * replay_FW_Message_type: Indicates which instruction or variable pass to DMUB |
| * cmd_data: Value of the config. |
| */ |
| bool edp_send_replay_cmd(struct dc_link *link, |
| enum replay_FW_Message_type msg, |
| union dmub_replay_cmd_set *cmd_data) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_replay *replay = dc->res_pool->replay; |
| unsigned int panel_inst; |
| |
| if (!replay) |
| return false; |
| |
| DC_LOGGER_INIT(link->ctx->logger); |
| |
| if (dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| cmd_data->panel_inst = panel_inst; |
| else { |
| DC_LOG_DC("%s(): get edp panel inst fail ", __func__); |
| return false; |
| } |
| |
| replay->funcs->replay_send_cmd(replay, msg, cmd_data); |
| |
| return true; |
| } |
| |
| bool edp_set_coasting_vtotal(struct dc_link *link, uint32_t coasting_vtotal) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_replay *replay = dc->res_pool->replay; |
| unsigned int panel_inst; |
| |
| if (!replay) |
| return false; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| if (coasting_vtotal && link->replay_settings.coasting_vtotal != coasting_vtotal) { |
| replay->funcs->replay_set_coasting_vtotal(replay, coasting_vtotal, panel_inst); |
| link->replay_settings.coasting_vtotal = coasting_vtotal; |
| } |
| |
| return true; |
| } |
| |
| bool edp_replay_residency(const struct dc_link *link, |
| unsigned int *residency, const bool is_start, const bool is_alpm) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_replay *replay = dc->res_pool->replay; |
| unsigned int panel_inst; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| if (replay != NULL && link->replay_settings.replay_feature_enabled) |
| replay->funcs->replay_residency(replay, panel_inst, residency, is_start, is_alpm); |
| else |
| *residency = 0; |
| |
| return true; |
| } |
| |
| bool edp_set_replay_power_opt_and_coasting_vtotal(struct dc_link *link, |
| const unsigned int *power_opts, uint32_t coasting_vtotal) |
| { |
| struct dc *dc = link->ctx->dc; |
| struct dmub_replay *replay = dc->res_pool->replay; |
| unsigned int panel_inst; |
| |
| if (!dc_get_edp_link_panel_inst(dc, link, &panel_inst)) |
| return false; |
| |
| /* Only both power and coasting vtotal changed, this func could return true */ |
| if (power_opts && link->replay_settings.replay_power_opt_active != *power_opts && |
| coasting_vtotal && link->replay_settings.coasting_vtotal != coasting_vtotal) { |
| if (link->replay_settings.replay_feature_enabled && |
| replay->funcs->replay_set_power_opt_and_coasting_vtotal) { |
| replay->funcs->replay_set_power_opt_and_coasting_vtotal(replay, |
| *power_opts, panel_inst, coasting_vtotal); |
| link->replay_settings.replay_power_opt_active = *power_opts; |
| link->replay_settings.coasting_vtotal = coasting_vtotal; |
| } else |
| return false; |
| } else |
| return false; |
| |
| return true; |
| } |
| |
| static struct abm *get_abm_from_stream_res(const struct dc_link *link) |
| { |
| int i; |
| struct dc *dc = link->ctx->dc; |
| struct abm *abm = NULL; |
| |
| for (i = 0; i < MAX_PIPES; i++) { |
| struct pipe_ctx pipe_ctx = dc->current_state->res_ctx.pipe_ctx[i]; |
| struct dc_stream_state *stream = pipe_ctx.stream; |
| |
| if (stream && stream->link == link) { |
| abm = pipe_ctx.stream_res.abm; |
| break; |
| } |
| } |
| return abm; |
| } |
| |
| int edp_get_backlight_level(const struct dc_link *link) |
| { |
| struct abm *abm = get_abm_from_stream_res(link); |
| struct panel_cntl *panel_cntl = link->panel_cntl; |
| struct dc *dc = link->ctx->dc; |
| struct dmcu *dmcu = dc->res_pool->dmcu; |
| bool fw_set_brightness = true; |
| |
| if (dmcu) |
| fw_set_brightness = dmcu->funcs->is_dmcu_initialized(dmcu); |
| |
| if (!fw_set_brightness && panel_cntl->funcs->get_current_backlight) |
| return panel_cntl->funcs->get_current_backlight(panel_cntl); |
| else if (abm != NULL && abm->funcs->get_current_backlight != NULL) |
| return (int) abm->funcs->get_current_backlight(abm); |
| else |
| return DC_ERROR_UNEXPECTED; |
| } |
| |
| int edp_get_target_backlight_pwm(const struct dc_link *link) |
| { |
| struct abm *abm = get_abm_from_stream_res(link); |
| |
| if (abm == NULL || abm->funcs->get_target_backlight == NULL) |
| return DC_ERROR_UNEXPECTED; |
| |
| return (int) abm->funcs->get_target_backlight(abm); |
| } |