diff options
author | Harry Wentland <harry.wentland@amd.com> | 2017-09-12 15:58:20 -0400 |
---|---|---|
committer | Alex Deucher <alexander.deucher@amd.com> | 2017-09-26 17:01:32 -0400 |
commit | 4562236b3bc0a28aeb6ee93b2d8a849a4c4e1c7c (patch) | |
tree | 84301c04dcaaa05c3318a8fe62cf62ab52ecc162 /drivers/gpu/drm/amd/display/modules/power/power.c | |
parent | 9c5b2b0d409304c2e3c1f4d1c9bb4958e1d46f8f (diff) |
drm/amd/dc: Add dc display driver (v2)
Supported DCE versions: 8.0, 10.0, 11.0, 11.2
v2: rebase against 4.11
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Acked-by: Alex Deucher <alexander.deucher@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Diffstat (limited to 'drivers/gpu/drm/amd/display/modules/power/power.c')
-rw-r--r-- | drivers/gpu/drm/amd/display/modules/power/power.c | 784 |
1 files changed, 784 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c new file mode 100644 index 000000000000..ea07e847da0a --- /dev/null +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -0,0 +1,784 @@ +/* + * 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 "mod_power.h" +#include "dm_services.h" +#include "dc.h" +#include "core_types.h" +#include "core_dc.h" + +#define MOD_POWER_MAX_CONCURRENT_SINKS 32 +#define SMOOTH_BRIGHTNESS_ADJUSTMENT_TIME_IN_MS 500 + +struct sink_caps { + const struct dc_sink *sink; +}; + +struct backlight_state { + unsigned int backlight; + unsigned int frame_ramp; + bool smooth_brightness_enabled; +}; + +struct core_power { + struct mod_power public; + struct dc *dc; + int num_sinks; + struct sink_caps *caps; + struct backlight_state *state; +}; + +union dmcu_abm_set_bl_params { + struct { + unsigned int gradual_change : 1; /* [0:0] */ + unsigned int reserved : 15; /* [15:1] */ + unsigned int frame_ramp : 16; /* [31:16] */ + } bits; + unsigned int u32All; +}; + +/* Backlight cached properties */ +static unsigned int backlight_8bit_lut_array[101]; +static unsigned int ac_level_percentage; +static unsigned int dc_level_percentage; +static bool backlight_caps_valid; +/* we use lazy initialization of backlight capabilities cache */ +static bool backlight_caps_initialized; +/* AC/DC levels initialized later in separate context */ +static bool backlight_def_levels_valid; + +/* ABM cached properties */ +static unsigned int abm_level; +static bool abm_user_enable; +static bool abm_active; + +/*PSR cached properties*/ +static unsigned int block_psr; + +/* Defines default backlight curve F(x) = A(x*x) + Bx + C. + * + * Backlight curve should always satisfy F(0) = min, F(100) = max, + * so polynom coefficients are: + * A is 0.0255 - B/100 - min/10000 - (255-max)/10000 = (max - min)/10000 - B/100 + * B is adjustable factor to modify the curve. + * Bigger B results in less concave curve. B range is [0..(max-min)/100] + * C is backlight minimum + */ +static const unsigned int backlight_curve_coeff_a_factor = 10000; +static const unsigned int backlight_curve_coeff_b = 100; +static const unsigned int backlight_curve_coeff_b_factor = 100; + +/* Minimum and maximum backlight input signal levels */ +static const unsigned int default_min_backlight = 12; +static const unsigned int default_max_backlight = 255; + +/* Other backlight constants */ +static const unsigned int absolute_backlight_max = 255; + +#define MOD_POWER_TO_CORE(mod_power)\ + container_of(mod_power, struct core_power, public) + +static bool check_dc_support(const struct dc *dc) +{ + if (dc->stream_funcs.set_backlight == NULL) + return false; + + return true; +} + +/* Given a specific dc_sink* this function finds its equivalent + * on the dc_sink array and returns the corresponding index + */ +static unsigned int sink_index_from_sink(struct core_power *core_power, + const struct dc_sink *sink) +{ + unsigned int index = 0; + + for (index = 0; index < core_power->num_sinks; index++) + if (core_power->caps[index].sink == sink) + return index; + + /* Could not find sink requested */ + ASSERT(false); + return index; +} + +static unsigned int convertBL8to17(unsigned int backlight_8bit) +{ + unsigned int temp_ulong = backlight_8bit * 0x10101; + unsigned char temp_uchar = + (unsigned char)(((temp_ulong & 0x80) >> 7) & 1); + + temp_ulong = (temp_ulong >> 8) + temp_uchar; + + return temp_ulong; +} + +static uint16_t convertBL8to16(unsigned int backlight_8bit) +{ + return (uint16_t)((backlight_8bit * 0x10101) >> 8); +} + +/*This is used when OS wants to retrieve the current BL. + * We return the 8bit value to OS. + */ +static unsigned int convertBL17to8(unsigned int backlight_17bit) +{ + if (backlight_17bit & 0x10000) + return default_max_backlight; + else + return (backlight_17bit >> 8); +} + +struct mod_power *mod_power_create(struct dc *dc) +{ + struct core_power *core_power = + dm_alloc(sizeof(struct core_power)); + + struct core_dc *core_dc = DC_TO_CORE(dc); + + int i = 0; + + if (core_power == NULL) + goto fail_alloc_context; + + core_power->caps = dm_alloc(sizeof(struct sink_caps) * + MOD_POWER_MAX_CONCURRENT_SINKS); + + if (core_power->caps == NULL) + goto fail_alloc_caps; + + for (i = 0; i < MOD_POWER_MAX_CONCURRENT_SINKS; i++) + core_power->caps[i].sink = NULL; + + core_power->state = dm_alloc(sizeof(struct backlight_state) * + MOD_POWER_MAX_CONCURRENT_SINKS); + + if (core_power->state == NULL) + goto fail_alloc_state; + + core_power->num_sinks = 0; + backlight_caps_valid = false; + + if (dc == NULL) + goto fail_construct; + + core_power->dc = dc; + + if (!check_dc_support(dc)) + goto fail_construct; + + abm_user_enable = false; + abm_active = false; + + return &core_power->public; + +fail_construct: + dm_free(core_power->state); + +fail_alloc_state: + dm_free(core_power->caps); + +fail_alloc_caps: + dm_free(core_power); + +fail_alloc_context: + return NULL; +} + + +void mod_power_destroy(struct mod_power *mod_power) +{ + if (mod_power != NULL) { + int i; + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + + dm_free(core_power->state); + + for (i = 0; i < core_power->num_sinks; i++) + dc_sink_release(core_power->caps[i].sink); + + dm_free(core_power->caps); + + dm_free(core_power); + } +} + +bool mod_power_add_sink(struct mod_power *mod_power, + const struct dc_sink *sink) +{ + if (sink->sink_signal == SIGNAL_TYPE_VIRTUAL) + return false; + + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + struct core_dc *core_dc = DC_TO_CORE(core_power->dc); + + if (core_power->num_sinks < MOD_POWER_MAX_CONCURRENT_SINKS) { + dc_sink_retain(sink); + core_power->caps[core_power->num_sinks].sink = sink; + core_power->state[core_power->num_sinks]. + smooth_brightness_enabled = false; + core_power->state[core_power->num_sinks]. + backlight = 100; + core_power->num_sinks++; + return true; + } + + return false; +} + +bool mod_power_remove_sink(struct mod_power *mod_power, + const struct dc_sink *sink) +{ + int i = 0, j = 0; + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + + for (i = 0; i < core_power->num_sinks; i++) { + if (core_power->caps[i].sink == sink) { + /* To remove this sink, shift everything after down */ + for (j = i; j < core_power->num_sinks - 1; j++) { + core_power->caps[j].sink = + core_power->caps[j + 1].sink; + + memcpy(&core_power->state[j], + &core_power->state[j + 1], + sizeof(struct backlight_state)); + } + core_power->num_sinks--; + dc_sink_release(sink); + return true; + } + } + return false; +} + +bool mod_power_set_backlight(struct mod_power *mod_power, + const struct dc_stream **streams, int num_streams, + unsigned int backlight_8bit) +{ + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + + unsigned int frame_ramp = 0; + + unsigned int stream_index, sink_index, vsync_rate_hz; + + union dmcu_abm_set_bl_params params; + + for (stream_index = 0; stream_index < num_streams; stream_index++) { + if (streams[stream_index]->sink->sink_signal == SIGNAL_TYPE_VIRTUAL) { + core_power->state[sink_index].backlight = 0; + core_power->state[sink_index].frame_ramp = 0; + core_power->state[sink_index].smooth_brightness_enabled = false; + continue; + } + + sink_index = sink_index_from_sink(core_power, + streams[stream_index]->sink); + + vsync_rate_hz = div64_u64(div64_u64((streams[stream_index]-> + timing.pix_clk_khz * 1000), + streams[stream_index]->timing.v_total), + streams[stream_index]->timing.h_total); + + core_power->state[sink_index].backlight = backlight_8bit; + + if (core_power->state[sink_index].smooth_brightness_enabled) + frame_ramp = ((vsync_rate_hz * + SMOOTH_BRIGHTNESS_ADJUSTMENT_TIME_IN_MS) + 500) + / 1000; + else + frame_ramp = 0; + + core_power->state[sink_index].frame_ramp = frame_ramp; + } + + params.u32All = 0; + params.bits.gradual_change = (frame_ramp > 0); + params.bits.frame_ramp = frame_ramp; + + core_power->dc->stream_funcs.set_backlight + (core_power->dc, backlight_8bit, params.u32All, streams[0]); + + return true; +} + +bool mod_power_get_backlight(struct mod_power *mod_power, + const struct dc_sink *sink, + unsigned int *backlight_8bit) +{ + if (sink->sink_signal == SIGNAL_TYPE_VIRTUAL) + return false; + + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + + unsigned int sink_index = sink_index_from_sink(core_power, sink); + + *backlight_8bit = core_power->state[sink_index].backlight; + + return true; +} + +/* hard coded to default backlight curve. */ +void mod_power_initialize_backlight_caps(struct mod_power + *mod_power) +{ + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + struct core_dc *core_dc = DC_TO_CORE(core_power->dc); + unsigned int i; + + backlight_caps_initialized = true; + + struct dm_acpi_atif_backlight_caps *pExtCaps = NULL; + bool customCurvePresent = false; + bool customMinMaxPresent = false; + bool customDefLevelsPresent = false; + + /* Allocate memory for ATIF output + * (do not want to use 256 bytes on the stack) + */ + pExtCaps = (struct dm_acpi_atif_backlight_caps *) + (dm_alloc(sizeof(struct dm_acpi_atif_backlight_caps))); + if (pExtCaps == NULL) + return; + + /* Retrieve ACPI extended brightness caps */ + if (dm_query_extended_brightness_caps + (core_dc->ctx, AcpiDisplayType_LCD1, pExtCaps)) { + ac_level_percentage = pExtCaps->acLevelPercentage; + dc_level_percentage = pExtCaps->dcLevelPercentage; + customMinMaxPresent = true; + customDefLevelsPresent = true; + customCurvePresent = (pExtCaps->numOfDataPoints > 0); + + ASSERT(pExtCaps->numOfDataPoints <= 99); + } else { + dm_free(pExtCaps); + return; + } + + if (customMinMaxPresent) + backlight_8bit_lut_array[0] = pExtCaps->minInputSignal; + else + backlight_8bit_lut_array[0] = default_min_backlight; + + if (customMinMaxPresent) + backlight_8bit_lut_array[100] = pExtCaps->maxInputSignal; + else + backlight_8bit_lut_array[100] = default_max_backlight; + + ASSERT(backlight_8bit_lut_array[100] <= absolute_backlight_max); + ASSERT(backlight_8bit_lut_array[0] <= + backlight_8bit_lut_array[100]); + + /* Just to make sure we use valid values */ + if (backlight_8bit_lut_array[100] > absolute_backlight_max) + backlight_8bit_lut_array[100] = absolute_backlight_max; + if (backlight_8bit_lut_array[0] > backlight_8bit_lut_array[100]) { + unsigned int swap; + + swap = backlight_8bit_lut_array[0]; + backlight_8bit_lut_array[0] = backlight_8bit_lut_array[100]; + backlight_8bit_lut_array[100] = swap; + } + + /* Build backlight translation table for custom curve */ + if (customCurvePresent) { + unsigned int index = 1; + unsigned int numOfDataPoints = + (pExtCaps->numOfDataPoints <= 99 ? + pExtCaps->numOfDataPoints : 99); + + /* Filling translation table from data points - + * between every two provided data points we + * lineary interpolate missing values + */ + for (i = 0; i < numOfDataPoints; i++) { + /* Clamp signal level between min and max + * (since min and max might come other + * soruce like registry) + */ + unsigned int luminance = + pExtCaps->dataPoints[i].luminance; + unsigned int signalLevel = + pExtCaps->dataPoints[i].signalLevel; + + if (signalLevel < backlight_8bit_lut_array[0]) + signalLevel = backlight_8bit_lut_array[0]; + if (signalLevel > backlight_8bit_lut_array[100]) + signalLevel = backlight_8bit_lut_array[100]; + + /* Lineary interpolate missing values */ + if (index < luminance) { + unsigned int baseValue = + backlight_8bit_lut_array[index-1]; + unsigned int deltaSignal = + signalLevel - baseValue; + unsigned int deltaLuma = + luminance - index + 1; + unsigned int step = deltaSignal; + + for (; index < luminance; index++) { + backlight_8bit_lut_array[index] = + baseValue + (step / deltaLuma); + step += deltaSignal; + } + } + + /* Now [index == luminance], + * so we can add data point to the translation table + */ + backlight_8bit_lut_array[index++] = signalLevel; + } + + /* Complete the final segment of interpolation - + * between last datapoint and maximum value + */ + if (index < 100) { + unsigned int baseValue = + backlight_8bit_lut_array[index-1]; + unsigned int deltaSignal = + backlight_8bit_lut_array[100] - + baseValue; + unsigned int deltaLuma = 100 - index + 1; + unsigned int step = deltaSignal; + + for (; index < 100; index++) { + backlight_8bit_lut_array[index] = + baseValue + (step / deltaLuma); + step += deltaSignal; + } + } + /* Build backlight translation table based on default curve */ + } else { + unsigned int delta = + backlight_8bit_lut_array[100] - + backlight_8bit_lut_array[0]; + unsigned int coeffC = backlight_8bit_lut_array[0]; + unsigned int coeffB = + (backlight_curve_coeff_b < delta ? + backlight_curve_coeff_b : delta); + unsigned int coeffA = delta - coeffB; /* coeffB is B*100 */ + + for (i = 1; i < 100; i++) { + backlight_8bit_lut_array[i] = + (coeffA * i * i) / + backlight_curve_coeff_a_factor + + (coeffB * i) / + backlight_curve_coeff_b_factor + + coeffC; + } + } + + if (pExtCaps != NULL) + dm_free(pExtCaps); + + /* Successfully initialized */ + backlight_caps_valid = true; + backlight_def_levels_valid = customDefLevelsPresent; +} + +unsigned int mod_power_backlight_level_percentage_to_signal( + struct mod_power *mod_power, unsigned int percentage) +{ + /* Do lazy initialization of backlight capabilities*/ + if (!backlight_caps_initialized) + mod_power_initialize_backlight_caps(mod_power); + + /* Since the translation table is indexed by percentage, + * we simply return backlight value at given percent + */ + if (backlight_caps_valid && percentage <= 100) + return backlight_8bit_lut_array[percentage]; + + return -1; +} + +unsigned int mod_power_backlight_level_signal_to_percentage( + struct mod_power *mod_power, + unsigned int signalLevel8bit) +{ + unsigned int invalid_backlight = (unsigned int)(-1); + /* Do lazy initialization of backlight capabilities */ + if (!backlight_caps_initialized) + mod_power_initialize_backlight_caps(mod_power); + + /* If customer curve cannot convert to differentiated value near min + * it is important to report 0 for min signal to pass setting "Dimmed" + * setting in HCK brightness2 tests. + */ + if (signalLevel8bit <= backlight_8bit_lut_array[0]) + return 0; + + /* Since the translation table is indexed by percentage + * we need to do a binary search over the array + * Another option would be to guess entry based on linear distribution + * and then do linear search in correct direction + */ + if (backlight_caps_valid && signalLevel8bit <= + absolute_backlight_max) { + unsigned int min = 0; + unsigned int max = 100; + unsigned int mid = invalid_backlight; + + while (max >= min) { + mid = (min + max) / 2; /* floor of half range */ + + if (backlight_8bit_lut_array[mid] < signalLevel8bit) + min = mid + 1; + else if (backlight_8bit_lut_array[mid] > + signalLevel8bit) + max = mid - 1; + else + break; + + if (max == 0 || max == 1) + return invalid_backlight; + } + return mid; + } + + return invalid_backlight; +} + + +bool mod_power_get_panel_backlight_boundaries( + struct mod_power *mod_power, + unsigned int *min_backlight, + unsigned int *max_backlight, + unsigned int *output_ac_level_percentage, + unsigned int *output_dc_level_percentage) +{ + /* Do lazy initialization of backlight capabilities */ + if (!backlight_caps_initialized) + mod_power_initialize_backlight_caps(mod_power); + + /* If cache was successfully updated, + * copy the values to output structure and return success + */ + if (backlight_caps_valid) { + *min_backlight = backlight_8bit_lut_array[0]; + *max_backlight = backlight_8bit_lut_array[100]; + + *output_ac_level_percentage = ac_level_percentage; + *output_dc_level_percentage = dc_level_percentage; + + return true; + } + + return false; +} + +bool mod_power_set_smooth_brightness(struct mod_power *mod_power, + const struct dc_sink *sink, bool enable_brightness) +{ + if (sink->sink_signal == SIGNAL_TYPE_VIRTUAL) + return false; + + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + unsigned int sink_index = sink_index_from_sink(core_power, sink); + + core_power->state[sink_index].smooth_brightness_enabled + = enable_brightness; + return true; +} + +bool mod_power_notify_mode_change(struct mod_power *mod_power, + const struct dc_stream *stream) +{ + if (stream->sink->sink_signal == SIGNAL_TYPE_VIRTUAL) + return false; + + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + + unsigned int sink_index = sink_index_from_sink(core_power, + stream->sink); + unsigned int frame_ramp = core_power->state[sink_index].frame_ramp; + union dmcu_abm_set_bl_params params; + + params.u32All = 0; + params.bits.gradual_change = (frame_ramp > 0); + params.bits.frame_ramp = frame_ramp; + + core_power->dc->stream_funcs.set_backlight + (core_power->dc, + core_power->state[sink_index].backlight, + params.u32All, stream); + + core_power->dc->stream_funcs.setup_psr + (core_power->dc, stream); + + return true; +} + + +static bool mod_power_abm_feature_enable(struct mod_power + *mod_power, bool enable) +{ + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + if (abm_user_enable == enable) + return true; + + abm_user_enable = enable; + + if (enable) { + if (abm_level != 0 && abm_active) + core_power->dc->stream_funcs.set_abm_level + (core_power->dc, abm_level); + } else { + if (abm_level != 0 && abm_active) { + abm_level = 0; + core_power->dc->stream_funcs.set_abm_level + (core_power->dc, abm_level); + } + } + + return true; +} + +static bool mod_power_abm_activate(struct mod_power + *mod_power, bool activate) +{ + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + if (abm_active == activate) + return true; + + abm_active = activate; + + if (activate) { + if (abm_level != 0 && abm_user_enable) + core_power->dc->stream_funcs.set_abm_level + (core_power->dc, abm_level); + } else { + if (abm_level != 0 && abm_user_enable) { + abm_level = 0; + core_power->dc->stream_funcs.set_abm_level + (core_power->dc, abm_level); + } + } + + return true; +} + +static bool mod_power_abm_set_level(struct mod_power *mod_power, + unsigned int level) +{ + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + if (abm_level == level) + return true; + + if (abm_active && abm_user_enable && level == 0) + core_power->dc->stream_funcs.set_abm_level + (core_power->dc, 0); + else if (abm_active && abm_user_enable && level != 0) + core_power->dc->stream_funcs.set_abm_level + (core_power->dc, level); + + abm_level = level; + + return true; +} + +bool mod_power_varibright_control(struct mod_power *mod_power, + struct varibright_info *input_varibright_info) +{ + switch (input_varibright_info->cmd) { + case VariBright_Cmd__SetVBLevel: + { + /* Set VariBright user level. */ + mod_power_abm_set_level(mod_power, + input_varibright_info->level); + } + break; + + case VariBright_Cmd__UserEnable: + { + /* Set VariBright user enable state. */ + mod_power_abm_feature_enable(mod_power, + input_varibright_info->enable); + } + break; + + case VariBright_Cmd__PostDisplayConfigChange: + { + /* Set VariBright user level. */ + mod_power_abm_set_level(mod_power, + input_varibright_info->level); + + /* Set VariBright user enable state. */ + mod_power_abm_feature_enable(mod_power, + input_varibright_info->enable); + + /* Set VariBright activate based on power state. */ + mod_power_abm_activate(mod_power, + input_varibright_info->activate); + } + break; + + default: + { + return false; + } + break; + } + + return true; +} + +bool mod_power_block_psr(bool block_enable, enum dmcu_block_psr_reason reason) +{ + if (block_enable) + block_psr |= reason; + else + block_psr &= ~reason; + + return true; +} + + +bool mod_power_set_psr_enable(struct mod_power *mod_power, + bool psr_enable) +{ + struct core_power *core_power = + MOD_POWER_TO_CORE(mod_power); + + if (block_psr == 0) + return core_power->dc->stream_funcs.set_psr_enable + (core_power->dc, psr_enable); + + return false; +} + + |