| /* | |
| * Copyright 2016 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 "dm_services.h" | |
| #include "dc.h" | |
| #include "mod_power.h" | |
| #include "core_types.h" | |
| #include "dmcu.h" | |
| #include "abm.h" | |
| #include "power_helpers.h" | |
| #include "dce/dmub_psr.h" | |
| #include "dal_asic_id.h" | |
| #include "link_service.h" | |
| #include <linux/math.h> | |
| #define DC_TRACE_LEVEL_MESSAGE(...) /* do nothing */ | |
| #define DC_TRACE_LEVEL_MESSAGEP(...) /* do nothing */ | |
| #define MOD_POWER_MAX_CONCURRENT_STREAMS 32 | |
| #define SMOOTH_BRIGHTNESS_ADJUSTMENT_TIME_IN_MS 500 | |
| #define LOW_REFRESH_RATE_DURATION_US_UPPER_BOUND 25000 | |
| /* If system or panel does not report some sort of brightness percent to nits | |
| * mapping, we will use following default values so backlight control using | |
| * nits based interfaces will still work, but might not describe panel | |
| * correctly. In this case percentage based backlight control should ideally | |
| * be used. | |
| * Min = 5 nits | |
| * Max = 300 nits | |
| */ | |
| static const unsigned int pwr_default_min_brightness_millinits = 1000; | |
| static const unsigned int pwr_default_sdr_brightness_millinits = 270000; | |
| static const unsigned int default_ac_backlight_percent = 100; | |
| static const unsigned int default_dc_backlight_percent = 70; | |
| #define MOD_POWER_TO_CORE(mod_power)\ | |
| container_of(mod_power, struct core_power, mod_public) | |
| /* Given a specific dc_stream* this function finds its equivalent | |
| * on the core_freesync->map and returns the corresponding index | |
| */ | |
| unsigned int map_index_from_stream(struct core_power *core_power, | |
| const struct dc_stream_state *stream) | |
| { | |
| unsigned int index = 0; | |
| for (index = 0; index < core_power->num_entities; index++) { | |
| if (core_power->map[index].stream == stream) | |
| return index; | |
| } | |
| /* Could not find stream requested, this is not trivial, fix when hit*/ | |
| DC_TRACE_LEVEL_MESSAGE(DAL_TRACE_LEVEL_ERROR, | |
| WPP_BIT_FLAG_Firmware_PsrState, | |
| "map index from stream: ERROR: core_power=%p stream=%p", | |
| core_power, | |
| stream); | |
| ASSERT(false); | |
| /* We come here only when we can't map stream index. | |
| * In good cases, this would happen when we attempt to change | |
| * brightness before stream creation, in which case we create a | |
| * dummy stream with index 0. | |
| * With external monitor connected, the index passed from this return | |
| * is 1. Passing anything greater than 0 from here would always point | |
| * to bad memory. | |
| */ | |
| return 0; | |
| } | |
| bool mod_power_hw_init(struct mod_power *mod_power) | |
| { | |
| /* Call backlight initialization */ | |
| return mod_power_hw_init_backlight(mod_power); | |
| /* Future: Add other HW init here */ | |
| } | |
| struct mod_power *mod_power_create(struct dc *dc, | |
| struct mod_power_init_params *init_params, | |
| unsigned int edp_num) | |
| { | |
| struct core_power *core_power = NULL; | |
| int i = 0; | |
| unsigned int abm_max_config = 0; | |
| unsigned int inst = 0; | |
| bool is_brightness_range_valid = false; | |
| if (dc == NULL) | |
| goto fail_dc_null; | |
| core_power = kzalloc_obj(struct core_power); | |
| if (core_power == NULL) | |
| goto fail_alloc_context; | |
| core_power->edp_num = edp_num; | |
| core_power->map = kzalloc(sizeof(struct power_entity) * MOD_POWER_MAX_CONCURRENT_STREAMS, | |
| GFP_KERNEL); | |
| if (core_power->map == NULL) | |
| goto fail_alloc_map; | |
| for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) { | |
| core_power->map[i].stream = NULL; | |
| } | |
| for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) { | |
| core_power->map[i].psr_context = | |
| kzalloc_obj(struct mod_power_psr_context); | |
| if (core_power->map[i].psr_context == NULL) | |
| goto fail_construct; | |
| } | |
| core_power->psr_smu_optimizations_support = init_params->allow_psr_smu_optimizations; | |
| core_power->multi_disp_optimizations_support = init_params->allow_psr_multi_disp_optimizations; | |
| for (inst = 0; inst < edp_num; inst++) { | |
| core_power->bl_prop[inst].min_abm_backlight = | |
| init_params[inst].min_abm_backlight; | |
| core_power->bl_prop[inst].disable_fractional_pwm = | |
| init_params[inst].disable_fractional_pwm; | |
| core_power->bl_prop[inst].use_linear_backlight_curve = | |
| init_params[inst].use_linear_backlight_curve; | |
| core_power->bl_prop[inst].use_nits_based_brightness = | |
| init_params[inst].use_nits_based_brightness; | |
| core_power->bl_prop[inst].backlight_ramping_override = | |
| init_params[inst].backlight_ramping_override; | |
| core_power->bl_prop[inst].backlight_ramping_reduction = | |
| init_params[inst].backlight_ramping_reduction; | |
| core_power->bl_prop[inst].backlight_ramping_start = | |
| init_params[inst].backlight_ramping_start; | |
| core_power->bl_prop[inst].use_custom_backlight_caps = | |
| init_params[inst].use_custom_backlight_caps; | |
| core_power->bl_prop[inst].custom_backlight_caps_config_no = | |
| init_params[inst].custom_backlight_caps_config_no; | |
| // Do not allow less than 101 backlight levels | |
| if (init_params[inst].num_backlight_levels < 101) | |
| core_power->bl_prop[inst].num_backlight_levels = 101; | |
| else | |
| core_power->bl_prop[inst].num_backlight_levels = | |
| init_params[inst].num_backlight_levels; | |
| core_power->bl_prop[inst].backlight_lut = (unsigned int *) | |
| (kzalloc(sizeof(unsigned int) * | |
| core_power->bl_prop[inst].num_backlight_levels, GFP_KERNEL)); | |
| if (core_power->bl_prop[inst].backlight_lut == NULL) | |
| goto fail_alloc_backlight_array; | |
| } | |
| core_power->varibright_prop.varibright_active = false; | |
| core_power->varibright_prop.varibright_user_enable = | |
| init_params->def_varibright_enable; | |
| // Table of ABM levels here is 1-4, but level 0 also exists as 'off' | |
| if (init_params->varibright_level <= abm_defines_max_level) { | |
| core_power->varibright_prop.varibright_level = | |
| init_params->varibright_level; | |
| } else { | |
| core_power->varibright_prop.varibright_level = 3; | |
| } | |
| if (init_params->def_varibright_level <= abm_defines_max_level) { | |
| core_power->varibright_prop.def_varibright_level = | |
| init_params->def_varibright_level; | |
| } else { | |
| core_power->varibright_prop.def_varibright_level = 3; | |
| } | |
| // ABM used to contain 4 different configs. There is only 3 since ABM 2.3. | |
| if ((dc->res_pool->dmcu != NULL) && (dc->res_pool->dmcu->dmcu_version.abm_version < 0x23)) | |
| abm_max_config = 4; | |
| else | |
| abm_max_config = 3; | |
| if (init_params->abm_config_setting < abm_max_config) | |
| core_power->varibright_prop.varibright_config_setting = | |
| init_params->abm_config_setting; | |
| else | |
| core_power->varibright_prop.varibright_config_setting = 0; | |
| for (inst = 0; inst < edp_num; inst++) { | |
| core_power->bl_prop[inst].backlight_lut[0] = init_params[inst].min_backlight_pwm; | |
| core_power->bl_prop[inst].backlight_lut[ | |
| core_power->bl_prop[inst].num_backlight_levels-1] = | |
| init_params[inst].max_backlight_pwm; | |
| core_power->bl_prop[inst].min_backlight_pwm = init_params[inst].min_backlight_pwm; | |
| core_power->bl_prop[inst].max_backlight_pwm = init_params[inst].max_backlight_pwm; | |
| core_power->bl_prop[inst].ac_backlight_percent = | |
| default_ac_backlight_percent; | |
| core_power->bl_prop[inst].dc_backlight_percent = | |
| default_dc_backlight_percent; | |
| core_power->bl_prop[inst].backlight_caps_valid = false; | |
| if (core_power->bl_prop[inst].use_nits_based_brightness) { | |
| core_power->bl_prop[inst].min_brightness_millinits = | |
| init_params[inst].panel_min_millinits; | |
| core_power->bl_prop[inst].max_brightness_millinits = | |
| init_params[inst].panel_max_millinits; | |
| } else { | |
| core_power->bl_prop[inst].min_brightness_millinits = | |
| pwr_default_min_brightness_millinits; | |
| core_power->bl_prop[inst].max_brightness_millinits = | |
| pwr_default_sdr_brightness_millinits; | |
| } | |
| core_power->bl_prop[inst].backlight_range = | |
| core_power->bl_prop[inst].max_backlight_pwm- | |
| core_power->bl_prop[inst].min_backlight_pwm; | |
| core_power->bl_prop[inst].nits_range = | |
| core_power->bl_prop[inst].max_brightness_millinits - | |
| core_power->bl_prop[inst].min_brightness_millinits; | |
| core_power->bl_state[inst].smooth_brightness_enabled = true; | |
| } | |
| /* Check if at least 1 instance in core_power is populated before failing */ | |
| for (inst = 0; inst < edp_num; inst++) { | |
| if (core_power->bl_prop[inst].nits_range != 0 && core_power->bl_prop[inst].backlight_range != 0) { | |
| is_brightness_range_valid = true; | |
| break; | |
| } | |
| } | |
| if (!is_brightness_range_valid) | |
| goto fail_bad_brightness_range; | |
| core_power->num_entities = 0; | |
| core_power->dc = dc; | |
| for (inst = 0; inst < edp_num; inst++) { | |
| initialize_backlight_caps(core_power, inst); | |
| core_power->bl_state[inst].backlight_millipercent = | |
| core_power->bl_prop[inst].dc_backlight_percent * 1000; | |
| core_power->bl_state[inst].backlight_pwm = backlight_millipercent_to_pwm(core_power, | |
| core_power->bl_state[inst].backlight_millipercent, inst); | |
| core_power->bl_state[inst].backlight_millinit = backlight_millipercent_to_millinit(core_power, | |
| core_power->bl_state[inst].backlight_millipercent, inst); | |
| } | |
| return &core_power->mod_public; | |
| fail_bad_brightness_range: | |
| fail_alloc_backlight_array: | |
| for (inst = 0; inst < edp_num; inst++) | |
| kfree(core_power->bl_prop[inst].backlight_lut); | |
| fail_construct: | |
| for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) | |
| kfree(core_power->map[i].psr_context); | |
| kfree(core_power->map); | |
| fail_alloc_map: | |
| kfree(core_power); | |
| fail_alloc_context: | |
| fail_dc_null: | |
| return NULL; | |
| } | |
| void mod_power_destroy(struct mod_power *mod_power) | |
| { | |
| if (mod_power != NULL) { | |
| unsigned int i; | |
| struct core_power *core_power = | |
| MOD_POWER_TO_CORE(mod_power); | |
| for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) | |
| kfree(core_power->map[i].psr_context); | |
| for (i = 0; i < core_power->num_entities; i++) | |
| if (core_power->map[i].stream) | |
| dc_stream_release(core_power->map[i].stream); | |
| kfree(core_power->map); | |
| for (i = 0; i < MAX_NUM_EDP; i++) | |
| kfree(core_power->bl_prop[i].backlight_lut); | |
| kfree(core_power); | |
| } | |
| } | |
| bool mod_power_add_stream(struct mod_power *mod_power, | |
| struct dc_stream_state *stream, struct psr_caps *caps) | |
| { | |
| struct core_power *core_power = NULL; | |
| if (mod_power == NULL) | |
| return false; | |
| core_power = MOD_POWER_TO_CORE(mod_power); | |
| if (core_power->num_entities < MOD_POWER_MAX_CONCURRENT_STREAMS) { | |
| dc_stream_retain(stream); | |
| core_power->map[core_power->num_entities].stream = stream; | |
| core_power->map[core_power->num_entities].caps = caps; | |
| // initialize cached PSR params to something "safe" (something that is | |
| // consistent with disabled PSR state) | |
| core_power->map[core_power->num_entities].psr_enabled = 0; | |
| core_power->map[core_power->num_entities].psr_events = psr_event_vsync; | |
| core_power->map[core_power->num_entities].psr_power_opt = 0; | |
| core_power->map[core_power->num_entities].replay_events = replay_event_vsync; | |
| core_power->num_entities++; | |
| return true; | |
| } | |
| DC_TRACE_LEVEL_MESSAGE(DAL_TRACE_LEVEL_ERROR, | |
| WPP_BIT_FLAG_Firmware_PsrState, | |
| "mod_power: add_stream: ERROR: stream=%p num_entities=%u >= MOD_POWER_MAX_CONCURRENT_STREAMS", | |
| stream, | |
| core_power->num_entities); | |
| return false; | |
| } | |
| bool mod_power_remove_stream(struct mod_power *mod_power, | |
| const struct dc_stream_state *stream) | |
| { | |
| unsigned int i = 0; | |
| struct core_power *core_power = NULL; | |
| unsigned int index = 0; | |
| if (mod_power == NULL) | |
| return false; | |
| core_power = MOD_POWER_TO_CORE(mod_power); | |
| if (core_power->num_entities == 0) { | |
| /* trying to remove a stream a second time or have not added yet */ | |
| BREAK_TO_DEBUGGER(); | |
| DC_TRACE_LEVEL_MESSAGE(DAL_TRACE_LEVEL_ERROR, | |
| WPP_BIT_FLAG_Firmware_PsrState, | |
| "mod_power: remove_stream: ERROR: num_entities=0 stream=%p", | |
| stream); | |
| return false; | |
| } | |
| index = map_index_from_stream(core_power, stream); | |
| if (index >= core_power->num_entities) { | |
| /* trying to remove a stream a second time or have not added yet */ | |
| BREAK_TO_DEBUGGER(); | |
| DC_TRACE_LEVEL_MESSAGE(DAL_TRACE_LEVEL_ERROR, | |
| WPP_BIT_FLAG_Firmware_PsrState, | |
| "mod_power: remove_stream: ERROR: index=%u >= num_entities=%u stream=%p", | |
| index, | |
| core_power->num_entities, | |
| stream); | |
| return false; | |
| } | |
| dc_stream_release(core_power->map[index].stream); | |
| core_power->map[index].stream = NULL; | |
| /* To remove this entity, shift everything after down */ | |
| for (i = index; i < core_power->num_entities - 1; i++) { | |
| core_power->map[i].stream = core_power->map[i + 1].stream; | |
| core_power->map[i].caps = core_power->map[i + 1].caps; | |
| // copy over cached parameters in case they map to PSR capable display | |
| core_power->map[i].psr_enabled = core_power->map[i + 1].psr_enabled; | |
| core_power->map[i].psr_events = core_power->map[i + 1].psr_events; | |
| core_power->map[i].psr_power_opt = core_power->map[i + 1].psr_power_opt; | |
| core_power->map[i].replay_events = core_power->map[i + 1].replay_events; | |
| memcpy(core_power->map[i].psr_context, core_power->map[i + 1].psr_context, sizeof(struct mod_power_psr_context)); | |
| memset(core_power->map[i + 1].psr_context, 0, sizeof(struct mod_power_psr_context)); | |
| } | |
| core_power->num_entities--; | |
| return true; | |
| } | |
| /* | |
| * Replace_stream should be used when there is a mode set for existing | |
| * display target with a valid stream. In this case might need to retain | |
| * cached PSR state (events, power opt, en/dis) if we are dealing with PSR | |
| * capable display. If mod_power_remove and mod_power_add are used instead, | |
| * then stream may be assigned to a different slot and may end up with | |
| * wrong cached PSR state. It is hard to tell which PSR events should | |
| * persist through mode set or what psr_events should be initialized to, so | |
| * it might be better just to retain them all. | |
| */ | |
| bool mod_power_replace_stream(struct mod_power *mod_power, | |
| const struct dc_stream_state *current_stream, | |
| struct dc_stream_state *new_stream, | |
| struct psr_caps *new_caps) | |
| { | |
| struct core_power *core_power = NULL; | |
| unsigned int index = 0; | |
| if (mod_power == NULL) | |
| return false; | |
| core_power = MOD_POWER_TO_CORE(mod_power); | |
| if (core_power->num_entities == 0) { | |
| /* no streams exist in the table yet */ | |
| BREAK_TO_DEBUGGER(); | |
| DC_TRACE_LEVEL_MESSAGE(DAL_TRACE_LEVEL_ERROR, | |
| WPP_BIT_FLAG_Firmware_PsrState, | |
| "mod_power: replace_stream: ERROR: num_entities=0 stream=%p", | |
| current_stream); | |
| return false; | |
| } | |
| index = map_index_from_stream(core_power, current_stream); | |
| if (index >= core_power->num_entities) { | |
| /* trying to replace a non-existent stream */ | |
| BREAK_TO_DEBUGGER(); | |
| DC_TRACE_LEVEL_MESSAGE(DAL_TRACE_LEVEL_ERROR, | |
| WPP_BIT_FLAG_Firmware_PsrState, | |
| "mod_power: replace_stream: ERROR: index=%u >= num_entities=%u stream=%p", | |
| index, | |
| core_power->num_entities, | |
| current_stream); | |
| return false; | |
| } | |
| dc_stream_release(core_power->map[index].stream); | |
| dc_stream_retain(new_stream); | |
| core_power->map[index].stream = new_stream; | |
| core_power->map[index].caps = new_caps; | |
| memset(core_power->map[index].psr_context, 0, sizeof(struct mod_power_psr_context)); | |
| return true; | |
| } | |
| bool mod_power_notify_mode_change(struct mod_power *mod_power, | |
| const struct dc_stream_state *stream, | |
| bool is_hdr) | |
| { | |
| unsigned int stream_index = 0; | |
| struct core_power *core_power = NULL; | |
| struct dc_link *link = NULL; | |
| struct dc *dc = NULL; | |
| unsigned int panel_inst = 0; | |
| uint8_t aux_inst = 0; | |
| if ((mod_power == NULL) || (stream == NULL)) | |
| return false; | |
| core_power = MOD_POWER_TO_CORE(mod_power); | |
| if (core_power->num_entities == 0) | |
| return false; | |
| stream_index = map_index_from_stream(core_power, stream); | |
| if (stream_index >= core_power->num_entities) | |
| return false; | |
| dc = core_power->dc; | |
| link = dc_stream_get_link(stream); | |
| if (link != NULL && dc_get_edp_link_panel_inst(dc, link, &panel_inst)) { | |
| aux_inst = link->dc->link_srv->get_ddc_aux_inst(link); | |
| mod_power_update_backlight_on_mode_change(core_power, link, panel_inst, aux_inst, is_hdr); | |
| /* Handle PSR notification */ | |
| mod_power_psr_notify_mode_change(mod_power, stream, link, stream_index); | |
| /* Handle Replay notification */ | |
| mod_power_replay_notify_mode_change(mod_power, dc, link, stream, stream_index); | |
| } | |
| return true; | |
| } | |
| bool mod_power_only_edp(const struct dc_state *context, const struct dc_stream_state *stream) | |
| { | |
| return context && context->stream_count == 1 && dc_is_embedded_signal(stream->signal); | |
| } |