From: Ethan Bitnun <etbit...@amd.com>

[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 <etbit...@amd.com>
Reviewed-by: Dillon Varone <dillon.var...@amd.com>
Acked-by: Aurabindo Pillai <aurabindo.pil...@amd.com>
Signed-off-by: Ethan Bitnun <etbit...@amd.com>
Tested-by: Daniel Wheeler <daniel.whee...@amd.com>
---
 .../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

Reply via email to