From: Ethan Bitnun <etbitnun@xxxxxxx> [Description] - Block FPO if the max stretch refresh rate is low enough to cause a flicker by storing the maximum safe refresh decrease from nominal in stream. - Brought over various Freesync Luminance functions to dc. Use these new functions to block fpo if we will flicker. - Generalized increase/reduce dependent functions to reduce code clutter and allow for easier use. - Added a debug option to enable the feature. Disabled by default. Co-authored-by: Ethan Bitnun <etbitnun@xxxxxxx> Reviewed-by: Dillon Varone <dillon.varone@xxxxxxx> Acked-by: Aurabindo Pillai <aurabindo.pillai@xxxxxxx> Signed-off-by: Ethan Bitnun <etbitnun@xxxxxxx> Tested-by: Daniel Wheeler <daniel.wheeler@xxxxxxx> --- .../gpu/drm/amd/display/dc/core/dc_stream.c | 228 ++++++++++++++++++ drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/dc_stream.h | 14 ++ .../gpu/drm/amd/display/dc/dc_stream_priv.h | 24 ++ .../display/dc/dcn32/dcn32_resource_helpers.c | 9 +- 5 files changed, 274 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index 5c7e4884cac2..d3201b0b3a09 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -35,6 +35,8 @@ #include "dc_stream_priv.h" #define DC_LOGGER dc->ctx->logger +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(x, y) ((x > y) ? x : y) /******************************************************************************* * Private functions @@ -781,3 +783,229 @@ void dc_stream_log(const struct dc *dc, const struct dc_stream_state *stream) } } +/* + * Finds the greatest index in refresh_rate_hz that contains a value <= refresh + */ +static int dc_stream_get_nearest_smallest_index(struct dc_stream_state *stream, int refresh) +{ + for (int i = 0; i < (LUMINANCE_DATA_TABLE_SIZE - 1); ++i) { + if ((stream->lumin_data.refresh_rate_hz[i] <= refresh) && (refresh < stream->lumin_data.refresh_rate_hz[i + 1])) { + return i; + } + } + return 9; +} + +/* + * Finds a corresponding brightness for a given refresh rate between 2 given indices, where index1 < index2 + */ +static int dc_stream_get_brightness_millinits_linear_interpolation (struct dc_stream_state *stream, + int index1, + int index2, + int refresh_hz) +{ + int slope = 0; + if (stream->lumin_data.refresh_rate_hz[index2] != stream->lumin_data.refresh_rate_hz[index1]) { + slope = (stream->lumin_data.luminance_millinits[index2] - stream->lumin_data.luminance_millinits[index1]) / + (stream->lumin_data.refresh_rate_hz[index2] - stream->lumin_data.refresh_rate_hz[index1]); + } + + int y_intercept = stream->lumin_data.luminance_millinits[index2] - slope * stream->lumin_data.refresh_rate_hz[index2]; + + return (y_intercept + refresh_hz * slope); +} + +/* + * Finds a corresponding refresh rate for a given brightness between 2 given indices, where index1 < index2 + */ +static int dc_stream_get_refresh_hz_linear_interpolation (struct dc_stream_state *stream, + int index1, + int index2, + int brightness_millinits) +{ + int slope = 1; + if (stream->lumin_data.refresh_rate_hz[index2] != stream->lumin_data.refresh_rate_hz[index1]) { + slope = (stream->lumin_data.luminance_millinits[index2] - stream->lumin_data.luminance_millinits[index1]) / + (stream->lumin_data.refresh_rate_hz[index2] - stream->lumin_data.refresh_rate_hz[index1]); + } + + int y_intercept = stream->lumin_data.luminance_millinits[index2] - slope * stream->lumin_data.refresh_rate_hz[index2]; + + return ((brightness_millinits - y_intercept) / slope); +} + +/* + * Finds the current brightness in millinits given a refresh rate + */ +static int dc_stream_get_brightness_millinits_from_refresh (struct dc_stream_state *stream, int refresh_hz) +{ + int nearest_smallest_index = dc_stream_get_nearest_smallest_index(stream, refresh_hz); + int nearest_smallest_value = stream->lumin_data.refresh_rate_hz[nearest_smallest_index]; + + if (nearest_smallest_value == refresh_hz) + return stream->lumin_data.luminance_millinits[nearest_smallest_index]; + + if (nearest_smallest_index >= 9) + return dc_stream_get_brightness_millinits_linear_interpolation(stream, nearest_smallest_index - 1, nearest_smallest_index, refresh_hz); + + if (nearest_smallest_value == stream->lumin_data.refresh_rate_hz[nearest_smallest_index + 1]) + return stream->lumin_data.luminance_millinits[nearest_smallest_index]; + + return dc_stream_get_brightness_millinits_linear_interpolation(stream, nearest_smallest_index, nearest_smallest_index + 1, refresh_hz); +} + +/* + * Finds the lowest refresh rate that can be achieved + * from starting_refresh_hz while staying within flicker criteria + */ +static int dc_stream_calculate_flickerless_refresh_rate(struct dc_stream_state *stream, + int current_brightness, + int starting_refresh_hz, + bool is_gaming, + bool search_for_max_increase) +{ + int nearest_smallest_index = dc_stream_get_nearest_smallest_index(stream, starting_refresh_hz); + + int flicker_criteria_millinits = is_gaming ? + stream->lumin_data.flicker_criteria_milli_nits_GAMING : + stream->lumin_data.flicker_criteria_milli_nits_STATIC; + + int safe_upper_bound = current_brightness + flicker_criteria_millinits; + int safe_lower_bound = current_brightness - flicker_criteria_millinits; + int lumin_millinits_temp = 0; + + int offset = -1; + if (search_for_max_increase) { + offset = 1; + } + + /* + * Increments up or down by 1 depending on search_for_max_increase + */ + for (int i = nearest_smallest_index; (i > 0 && !search_for_max_increase) || (i < (LUMINANCE_DATA_TABLE_SIZE - 1) && search_for_max_increase); i += offset) { + + lumin_millinits_temp = stream->lumin_data.luminance_millinits[i + offset]; + + if ((lumin_millinits_temp >= safe_upper_bound) || (lumin_millinits_temp <= safe_lower_bound)) { + + if (stream->lumin_data.refresh_rate_hz[i + offset] == stream->lumin_data.refresh_rate_hz[i]) + return stream->lumin_data.refresh_rate_hz[i]; + + int target_brightness = (stream->lumin_data.luminance_millinits[i + offset] >= (current_brightness + flicker_criteria_millinits)) ? + current_brightness + flicker_criteria_millinits : + current_brightness - flicker_criteria_millinits; + + int refresh = 0; + + /* + * Need the second input to be < third input for dc_stream_get_refresh_hz_linear_interpolation + */ + if (search_for_max_increase) + refresh = dc_stream_get_refresh_hz_linear_interpolation(stream, i, i + offset, target_brightness); + else + refresh = dc_stream_get_refresh_hz_linear_interpolation(stream, i + offset, i, target_brightness); + + if (refresh == stream->lumin_data.refresh_rate_hz[i + offset]) + return stream->lumin_data.refresh_rate_hz[i + offset]; + + return refresh; + } + } + + if (search_for_max_increase) + return stream->lumin_data.refresh_rate_hz[LUMINANCE_DATA_TABLE_SIZE - 1]; + else + return stream->lumin_data.refresh_rate_hz[0]; +} + +/* + * Gets the max delta luminance within a specified refresh range + */ +static int dc_stream_get_max_delta_lumin_millinits(struct dc_stream_state *stream, int hz1, int hz2, bool isGaming) +{ + int lower_refresh_brightness = dc_stream_get_brightness_millinits_from_refresh (stream, hz1); + int higher_refresh_brightness = dc_stream_get_brightness_millinits_from_refresh (stream, hz2); + + int min = lower_refresh_brightness; + int max = higher_refresh_brightness; + + /* + * Static screen, therefore no need to scan through array + */ + if (!isGaming) { + if (lower_refresh_brightness >= higher_refresh_brightness) { + return lower_refresh_brightness - higher_refresh_brightness; + } + return higher_refresh_brightness - lower_refresh_brightness; + } + + min = MIN(lower_refresh_brightness, higher_refresh_brightness); + max = MAX(lower_refresh_brightness, higher_refresh_brightness); + + int nearest_smallest_index = dc_stream_get_nearest_smallest_index(stream, hz1); + + for (; nearest_smallest_index < (LUMINANCE_DATA_TABLE_SIZE - 1) && + stream->lumin_data.refresh_rate_hz[nearest_smallest_index + 1] <= hz2 ; nearest_smallest_index++) { + min = MIN(min, stream->lumin_data.luminance_millinits[nearest_smallest_index + 1]); + max = MAX(max, stream->lumin_data.luminance_millinits[nearest_smallest_index + 1]); + } + + return (max - min); +} + +/* + * Finds the highest refresh rate that can be achieved + * from starting_refresh_hz while staying within flicker criteria + */ +int dc_stream_calculate_max_flickerless_refresh_rate(struct dc_stream_state *stream, int starting_refresh_hz, bool is_gaming) +{ + if (!stream->lumin_data.is_valid) + return 0; + + int current_brightness = dc_stream_get_brightness_millinits_from_refresh(stream, starting_refresh_hz); + + return dc_stream_calculate_flickerless_refresh_rate(stream, + current_brightness, + starting_refresh_hz, + is_gaming, + true); +} + +/* + * Finds the lowest refresh rate that can be achieved + * from starting_refresh_hz while staying within flicker criteria + */ +int dc_stream_calculate_min_flickerless_refresh_rate(struct dc_stream_state *stream, int starting_refresh_hz, bool is_gaming) +{ + if (!stream->lumin_data.is_valid) + return 0; + + int current_brightness = dc_stream_get_brightness_millinits_from_refresh(stream, starting_refresh_hz); + + return dc_stream_calculate_flickerless_refresh_rate(stream, + current_brightness, + starting_refresh_hz, + is_gaming, + false); +} + +/* + * Determines if there will be a flicker when moving between 2 refresh rates + */ +bool dc_stream_is_refresh_rate_range_flickerless(struct dc_stream_state *stream, int hz1, int hz2, bool is_gaming) +{ + + /* + * Assume that we wont flicker if there is invalid data + */ + if (!stream->lumin_data.is_valid) + return false; + + int dl = dc_stream_get_max_delta_lumin_millinits(stream, hz1, hz2, is_gaming); + + int flicker_criteria_millinits = (is_gaming) ? + stream->lumin_data.flicker_criteria_milli_nits_GAMING : + stream->lumin_data.flicker_criteria_milli_nits_STATIC; + + return (dl <= flicker_criteria_millinits); +} diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 9d235fc3525d..1e28a36a76e6 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -456,6 +456,7 @@ struct dc_config { bool allow_0_dtb_clk; bool use_assr_psp_message; bool support_edp0_on_dp1; + unsigned int enable_fpo_flicker_detection; }; enum visual_confirm { diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h index e5dbbc6089a5..3d0adf8838ca 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h @@ -160,6 +160,18 @@ struct dc_stream_debug_options { char force_odm_combine_segments; }; +#define LUMINANCE_DATA_TABLE_SIZE 10 + +struct luminance_data { + bool is_valid; + int refresh_rate_hz[LUMINANCE_DATA_TABLE_SIZE]; + int luminance_millinits[LUMINANCE_DATA_TABLE_SIZE]; + int flicker_criteria_milli_nits_GAMING; + int flicker_criteria_milli_nits_STATIC; + int nominal_refresh_rate; + int dm_max_decrease_from_nominal; +}; + struct dc_stream_state { // sink is deprecated, new code should not reference // this pointer @@ -286,6 +298,8 @@ struct dc_stream_state { bool vblank_synchronized; bool fpo_in_use; bool is_phantom; + + struct luminance_data lumin_data; }; #define ABM_LEVEL_IMMEDIATE_DISABLE 255 diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream_priv.h b/drivers/gpu/drm/amd/display/dc/dc_stream_priv.h index 7476fd52ce2b..ea13804f7b14 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream_priv.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream_priv.h @@ -34,4 +34,28 @@ void dc_stream_destruct(struct dc_stream_state *stream); void dc_stream_assign_stream_id(struct dc_stream_state *stream); +/* + * Finds the highest refresh rate that can be achieved + * from starting_freq while staying within flicker criteria + */ +int dc_stream_calculate_max_flickerless_refresh_rate(struct dc_stream_state *stream, + int starting_refresh_hz, + bool is_gaming); + +/* + * Finds the lowest refresh rate that can be achieved + * from starting_freq while staying within flicker criteria + */ +int dc_stream_calculate_min_flickerless_refresh_rate(struct dc_stream_state *stream, + int starting_refresh_hz, + bool is_gaming); + +/* + * Determines if there will be a flicker when moving between 2 refresh rates + */ +bool dc_stream_is_refresh_rate_range_flickerless(struct dc_stream_state *stream, + int hz1, + int hz2, + bool is_gaming); + #endif // _DC_STREAM_PRIV_H_ diff --git a/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_resource_helpers.c b/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_resource_helpers.c index fbcd6f7bc993..6472da2c361e 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_resource_helpers.c +++ b/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_resource_helpers.c @@ -29,6 +29,7 @@ #include "dml/dcn32/display_mode_vba_util_32.h" #include "dml/dcn32/dcn32_fpu.h" #include "dc_state_priv.h" +#include "dc_stream_priv.h" static bool is_dual_plane(enum surface_pixel_format format) { @@ -459,7 +460,7 @@ static int get_frame_rate_at_max_stretch_100hz( } static bool is_refresh_rate_support_mclk_switch_using_fw_based_vblank_stretch( - struct dc_stream_state *fpo_candidate_stream, uint32_t fpo_vactive_margin_us) + struct dc_stream_state *fpo_candidate_stream, uint32_t fpo_vactive_margin_us, int current_refresh_rate) { int refresh_rate_max_stretch_100hz; int min_refresh_100hz; @@ -473,6 +474,10 @@ static bool is_refresh_rate_support_mclk_switch_using_fw_based_vblank_stretch( if (refresh_rate_max_stretch_100hz < min_refresh_100hz) return false; + if (fpo_candidate_stream->ctx->dc->config.enable_fpo_flicker_detection > 0 && + !dc_stream_is_refresh_rate_range_flickerless(fpo_candidate_stream, (refresh_rate_max_stretch_100hz / 100), current_refresh_rate, false)) + return false; + return true; } @@ -569,7 +574,7 @@ struct dc_stream_state *dcn32_can_support_mclk_switch_using_fw_based_vblank_stre return NULL; fpo_vactive_margin_us = is_fpo_vactive ? dc->debug.fpo_vactive_margin_us : 0; // For now hardcode the FPO + Vactive stretch margin to be 2000us - if (!is_refresh_rate_support_mclk_switch_using_fw_based_vblank_stretch(fpo_candidate_stream, fpo_vactive_margin_us)) + if (!is_refresh_rate_support_mclk_switch_using_fw_based_vblank_stretch(fpo_candidate_stream, fpo_vactive_margin_us, refresh_rate)) return NULL; if (!fpo_candidate_stream->allow_freesync) -- 2.44.0