The v1 and v2 FreeSync infopackets carry the minimum and maximum
refresh rate in one byte each, and build_vrr_infopacket_data_v1() casts
the rate in Hz to unsigned char. On a sink whose range goes above
255 Hz the value wraps: a 320 Hz mode is advertised as a 64 Hz maximum.

Sinks that treat the field as authoritative misbehave once the wrapped
value lands inside their range. An MSI MAG 271QPX32 (48-320 Hz) over
native DP looks like a ~720p image upscaled to the full screen at 320 Hz
and runs its self-test pattern at 305-315 Hz, while 300 Hz (wrapping to
44 Hz, below its minimum) is fine. Capping the EDID range at 255 Hz, so
the packet says 255, gives a correct picture at 320 Hz.

Saturate the fields at 255 Hz instead of wrapping.

Capping v1 at 255 as a fallback was suggested by oystersuki in the linked
issue.

Fixes: 98e6436d3af5 ("drm/amd/display: Refactor FreeSync module")
Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5988
Cc: [email protected]
Signed-off-by: Cooper Morgan <[email protected]>
---
 .../amdgpu_dm/tests/amdgpu_dm_freesync_test.c | 57 +++++++++++++++++++
 .../amd/display/modules/freesync/freesync.c   | 12 +++-
 2 files changed, 66 insertions(+), 3 deletions(-)

diff --git 
a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
index 710cdc7f4..b603516e2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c
@@ -664,6 +664,62 @@ static void 
dm_test_update_freesync_state_pcon_default(struct kunit *test)
        dm_test_update_freesync_state_pcon_version(test, 0);
 }
 
+/*
+ * Build the VRR infopacket for a non-PCON, non-Replay sink with VRR inactive
+ * over 48Hz to @mode_hz, the max that mod_freesync_build_vrr_params() clamps
+ * to the mode's rate.
+ */
+static struct dc_info_packet
+dm_test_freesync_packet(struct kunit *test, enum signal_type signal,
+                       u8 vsdb_version, int sink_max_hz, int mode_hz)
+{
+       struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+       struct dc_link *link = dm_kunit_alloc_link(test);
+       struct dc_stream_state *stream = dm_kunit_alloc_stream(test, link);
+       struct amdgpu_dm_connector *aconnector;
+       struct mod_vrr_params *vrr;
+       struct amdgpu_crtc *acrtc;
+       struct dm_crtc_state crtc_state = { 0 };
+
+       acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, acrtc);
+       aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, aconnector);
+       crtc_state.base.crtc = &acrtc->base;
+       stream->timing.h_total = 2200;
+       stream->timing.v_total = 1125;
+       stream->signal = signal;
+       stream->dm_stream_context = aconnector;
+       aconnector->vsdb_info.amd_vsdb_version = vsdb_version;
+       aconnector->max_vfreq = sink_max_hz;
+       vrr = &acrtc->dm_irq_params.vrr_params;
+       vrr->send_info_frame = true;
+       vrr->state = VRR_STATE_INACTIVE;
+       vrr->min_refresh_in_uhz = 48000000;
+       vrr->max_refresh_in_uhz = mode_hz * 1000000;
+       adev->dm.adev = adev;
+
+       amdgpu_dm_update_freesync_state_on_stream(&adev->dm, &crtc_state, 
stream, NULL, 0);
+
+       return crtc_state.vrr_infopacket;
+}
+
+/**
+ * dm_test_update_freesync_state_dp_v2_saturates - Test v1 saturates above 
255Hz
+ * @test: The KUnit test context
+ *
+ * A VSDB v2 sink keeps the v1 packet, which cannot carry 320Hz.
+ */
+static void dm_test_update_freesync_state_dp_v2_saturates(struct kunit *test)
+{
+       struct dc_info_packet p =
+               dm_test_freesync_packet(test, SIGNAL_TYPE_DISPLAY_PORT, 2, 320, 
320);
+
+       KUNIT_EXPECT_EQ(test, p.hb3, 0x04);
+       KUNIT_EXPECT_EQ(test, p.sb[7], 255);
+       KUNIT_EXPECT_EQ(test, p.sb[8], 255);
+}
+
 /* Tests for amdgpu_dm_update_stream_irq_parameters() */
 
 /**
@@ -900,6 +956,7 @@ static struct kunit_case amdgpu_dm_freesync_tests[] = {
        KUNIT_CASE(dm_test_update_freesync_state_pcon_v2),
        KUNIT_CASE(dm_test_update_freesync_state_pcon_v3),
        KUNIT_CASE(dm_test_update_freesync_state_pcon_default),
+       KUNIT_CASE(dm_test_update_freesync_state_dp_v2_saturates),
        /* amdgpu_dm_update_stream_irq_parameters */
        KUNIT_CASE(dm_test_update_stream_irq_parameters_no_stream),
        KUNIT_CASE(dm_test_update_stream_irq_parameters_invalid_timing),
diff --git a/drivers/gpu/drm/amd/display/modules/freesync/freesync.c 
b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c
index 4bd060e30..af7054668 100644
--- a/drivers/gpu/drm/amd/display/modules/freesync/freesync.c
+++ b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c
@@ -571,6 +571,12 @@ static bool vrr_settings_require_update(struct 
core_freesync *core_freesync,
        return false;
 }
 
+/* v1/v2 packets hold each rate in one byte; saturate instead of wrapping. */
+static unsigned char vrr_refresh_hz_u8(unsigned int refresh_in_uhz)
+{
+       return min((refresh_in_uhz + 500000) / 1000000, 255U);
+}
+
 static void build_vrr_infopacket_data_v1(const struct mod_vrr_params *vrr,
                struct dc_info_packet *infopacket,
                bool freesync_on_desktop)
@@ -614,15 +620,15 @@ static void build_vrr_infopacket_data_v1(const struct 
mod_vrr_params *vrr,
        /* PB7 = FreeSync Minimum refresh rate (Hz) */
        if (vrr->state == VRR_STATE_ACTIVE_VARIABLE ||
                        vrr->state == VRR_STATE_ACTIVE_FIXED) {
-               infopacket->sb[7] = (unsigned char)((vrr->min_refresh_in_uhz + 
500000) / 1000000);
+               infopacket->sb[7] = vrr_refresh_hz_u8(vrr->min_refresh_in_uhz);
        } else {
-               infopacket->sb[7] = (unsigned char)((vrr->max_refresh_in_uhz + 
500000) / 1000000);
+               infopacket->sb[7] = vrr_refresh_hz_u8(vrr->max_refresh_in_uhz);
        }
 
        /* PB8 = FreeSync Maximum refresh rate (Hz)
         * Note: We should never go above the field rate of the mode timing set.
         */
-       infopacket->sb[8] = (unsigned char)((vrr->max_refresh_in_uhz + 500000) 
/ 1000000);
+       infopacket->sb[8] = vrr_refresh_hz_u8(vrr->max_refresh_in_uhz);
 }
 
 static void build_vrr_infopacket_data_v3(const struct mod_vrr_params *vrr,
-- 
2.56.0

Reply via email to