* [PATCH 0/2] drm/amd/display: Keep eDP native timing after resume with video=
@ 2026-10-14 22:30 Hugo Sales
2026-10-14 22:30 ` [PATCH 1/2] drm/amd/display: Fall back to the native mode, not the first, on eDP/LVDS Hugo Sales
2026-10-14 22:30 ` [PATCH 2/2] drm/amd/display: Add KUnit tests for the eDP/LVDS native mode fallback Hugo Sales
0 siblings, 2 replies; 4+ messages in thread
From: Hugo Sales @ 2026-10-14 22:30 UTC (permalink / raw)
To: amd-gfx
Cc: dri-devel, linux-kernel, harry.wentland, sunpeng.li, siqueira,
alexander.deucher, Hugo Sales
On a 3840x2400 eDP panel booted with video=eDP-1:1920x1200@60, an HDMI
hotplug followed by suspend/resume brings the panel back with the
1920x1200 mode as its raw timing. The image ends up squeezed into the
top-left corner. Photos, hardware details and the test loop are in
https://gitlab.freedesktop.org/drm/amd/-/issues/5983
The hotplug makes fbdev emulation reprobe every connector with its
framebuffer size as the limit, so connector->modes loses the 3840x2400
mode. On resume, create_stream_for_sink() finds no preferred mode in the
list and scales onto the first mode instead. Patch 1 falls back to the
encoder's native_mode, which amdgpu_dm_get_native_mode() copies from the
preferred mode before the size limits apply. Patch 2 adds KUnit tests
for it
The fallback only applies to eDP and LVDS, where amdgpu turns scaling on
by itself. On other connectors the preferred mode can also be missing
because mode_valid rejected it, and then scaling onto the first valid
mode is right. Those connectors still hit this bug when userspace sets
the scaling mode property, which this series doesn't fix
A fuller amdgpu fix would always take the eDP and LVDS native timing
from the encoder, and search connector->modes only when the encoder has
none. I kept this to a fallback so it changes less, but I can respin it
that way if you'd prefer
On amd-staging-drm-next, with an HDMI hotplug and suspend/resume
(pm_test=platform) in a loop, 28 of 29 resumes came back with the wrong
timing without the patch, and the other showed it briefly. With it, 169
of 169 were fine. A backport to 7.2.8 went from 103 of 103 bad to 1363
of 1363 good. The new KUnit tests fail without the fix, and the
connector suite passes, apart from dm_test_fs_caps_hdmi_vsdb and
dm_test_fs_caps_pcon_whitelist, which already fail on this tree
The cause could also be fixed in DRM core, for every connector, by
making drm_client_modeset_probe() probe with the device's limits and
apply the framebuffer size only when fbdev picks its own mode. That
touches more shared code, since fbdev's other mode pickers would then
need size checks too
Hugo Sales (2):
drm/amd/display: Fall back to the native mode, not the first, on
eDP/LVDS
drm/amd/display: Add KUnit tests for the eDP/LVDS native mode fallback
.../display/amdgpu_dm/amdgpu_dm_connector.c | 28 +++++
.../tests/amdgpu_dm_connector_test.c | 112 ++++++++++++++++++
2 files changed, 140 insertions(+)
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] drm/amd/display: Fall back to the native mode, not the first, on eDP/LVDS
2026-10-14 22:30 [PATCH 0/2] drm/amd/display: Keep eDP native timing after resume with video= Hugo Sales
@ 2026-10-14 22:30 ` Hugo Sales
2026-10-23 18:26 ` Chung, ChiaHsuan (Tom)
2026-10-14 22:30 ` [PATCH 2/2] drm/amd/display: Add KUnit tests for the eDP/LVDS native mode fallback Hugo Sales
1 sibling, 1 reply; 4+ messages in thread
From: Hugo Sales @ 2026-10-14 22:30 UTC (permalink / raw)
To: amd-gfx
Cc: dri-devel, linux-kernel, harry.wentland, sunpeng.li, siqueira,
alexander.deucher, Hugo Sales
Currently, create_stream_for_sink() takes the native timing for a scaled
mode from the preferred mode in connector->modes. After fbdev emulation
probes with size limits below the native resolution, as it does when
video= sets a smaller mode, that mode is no longer in the list
If that probe is the last one before a suspend, for example after an
HDMI hotplug, the resume finds no preferred mode, and the panel is
driven with the scaled mode's timing instead of its own
amdgpu_dm_get_native_mode() copies the preferred mode to the encoder on
every probe, before the size limits apply. Fall back to that copy when
connector->modes has no preferred mode. Limit this to eDP and LVDS,
where amdgpu turns scaling on by itself. On other connectors mode_valid
can also leave the preferred mode out, and then the first valid mode is
the right fallback
Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5983
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../display/amdgpu_dm/amdgpu_dm_connector.c | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 428a5f937..a1dc3e287 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -257,6 +257,8 @@ STATIC_IFN_KUNIT void update_subconnector_property(struct amdgpu_dm_connector *a
EXPORT_IF_KUNIT(update_subconnector_property);
STATIC_IFN_KUNIT int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
+STATIC_IFN_KUNIT struct drm_encoder *amdgpu_dm_connector_to_encoder(
+ struct drm_connector *connector);
STATIC_IFN_KUNIT void amdgpu_dm_fbc_init(struct drm_connector *connector)
{
@@ -1470,6 +1472,7 @@ create_stream_for_sink(struct drm_connector *connector,
struct dc_stream_state *stream = NULL;
struct drm_display_mode mode;
struct drm_display_mode saved_mode;
+ struct drm_display_mode native_mode;
struct drm_display_mode *freesync_mode = NULL;
bool native_mode_found = false;
bool recalculate_timing = false;
@@ -1529,6 +1532,31 @@ create_stream_for_sink(struct drm_connector *connector,
break;
}
}
+
+ /*
+ * After a probe with size limits smaller than the preferred mode,
+ * connector->modes no longer has that mode, and the first mode left
+ * would become the stream timing. fbdev emulation probes with limits
+ * that video= can set below the native size. The encoder has a copy of
+ * the preferred mode from amdgpu_dm_get_native_mode(), made on every
+ * probe before the limits apply, so use that copy. Only do this for eDP
+ * and LVDS panels, which take only their native timing. On other
+ * connectors mode_valid can also reject the preferred mode, and then
+ * the first valid mode is the right one
+ */
+ if (!native_mode_found && aconnector && !aconnector->mst_root && aconnector->drm_edid &&
+ (connector->connector_type == DRM_MODE_CONNECTOR_eDP ||
+ connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
+ struct drm_encoder *encoder = amdgpu_dm_connector_to_encoder(connector);
+
+ if (encoder && to_amdgpu_encoder(encoder)->native_mode.clock) {
+ drm_mode_copy(&native_mode, &to_amdgpu_encoder(encoder)->native_mode);
+ drm_mode_set_crtcinfo(&native_mode, CRTC_INTERLACE_HALVE_V);
+ preferred_mode = &native_mode;
+ native_mode_found = true;
+ }
+ }
+
if (!native_mode_found)
preferred_mode = list_first_entry_or_null(
&connector->modes,
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/2] drm/amd/display: Add KUnit tests for the eDP/LVDS native mode fallback
2026-10-14 22:30 [PATCH 0/2] drm/amd/display: Keep eDP native timing after resume with video= Hugo Sales
2026-10-14 22:30 ` [PATCH 1/2] drm/amd/display: Fall back to the native mode, not the first, on eDP/LVDS Hugo Sales
@ 2026-10-14 22:30 ` Hugo Sales
1 sibling, 0 replies; 4+ messages in thread
From: Hugo Sales @ 2026-10-14 22:30 UTC (permalink / raw)
To: amd-gfx
Cc: dri-devel, linux-kernel, harry.wentland, sunpeng.li, siqueira,
alexander.deucher, Hugo Sales
Leave only a scaled mode in connector->modes and put the larger native
mode in the encoder, as a size-limited probe does. On eDP and LVDS,
create_stream_for_sink() must build a stream with the native timing and
the scaled mode as its source size. On HDMI it must keep the first mode
in the list as the timing
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../tests/amdgpu_dm_connector_test.c | 112 ++++++++++++++++++
1 file changed, 112 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 56fd133bc..55e2c3849 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -4547,6 +4547,115 @@ static void dm_test_create_stream_hdmi_vsif(struct kunit *test)
dc_sink_release(sink);
}
+/*
+ * Build a stream after a probe with size limits below the native mode, such as
+ * fbdev emulation's. That probe leaves only a 1920x1200 scaled mode in the
+ * connector's mode list, while the encoder keeps the 3840x2400 native mode
+ */
+static struct dc_stream_state *
+dm_test_native_mode_pruned_stream(struct kunit *test, int connector_type)
+{
+ static const u8 edid[EDID_LENGTH];
+ struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
+ struct amdgpu_encoder *aenc;
+ struct dc_stream_state *stream;
+ struct drm_display_mode *native;
+
+ ctx->aconnector->base.connector_type = connector_type;
+
+ aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aenc);
+ KUNIT_ASSERT_EQ(test, drmm_encoder_init(ctx->drm, &aenc->base, NULL,
+ DRM_MODE_ENCODER_TMDS, NULL), 0);
+ KUNIT_ASSERT_EQ(test, drm_connector_attach_encoder(&ctx->aconnector->base, &aenc->base), 0);
+
+ native = &aenc->native_mode;
+ native->clock = 592500;
+ native->hdisplay = 3840;
+ native->hsync_start = 3888;
+ native->hsync_end = 3920;
+ native->htotal = 4000;
+ native->vdisplay = 2400;
+ native->vsync_start = 2403;
+ native->vsync_end = 2409;
+ native->vtotal = 2469;
+ /* Cached by the first, unlimited probe */
+ drm_mode_copy(&ctx->aconnector->freesync_vid_base, native);
+
+ drm_mode_copy(ctx->mode, native);
+ ctx->mode->hdisplay = 1920;
+ ctx->mode->vdisplay = 1200;
+ drm_mode_set_crtcinfo(ctx->mode, CRTC_INTERLACE_HALVE_V);
+ dm_test_stream_add_mode(test, ctx, 0);
+ ctx->dm_state->scaling = RMX_FULL;
+
+ ctx->aconnector->drm_edid = drm_edid_alloc(edid, sizeof(edid));
+ KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector->drm_edid);
+
+ stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
+
+ drm_edid_free(ctx->aconnector->drm_edid);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+
+ return stream;
+}
+
+/**
+ * dm_test_create_stream_native_mode_pruned_edp - Test eDP gets native timing
+ * @test: The KUnit test context
+ *
+ * The scaled mode must still be driven with the panel's native timing, kept in
+ * the encoder
+ */
+static void dm_test_create_stream_native_mode_pruned_edp(struct kunit *test)
+{
+ struct dc_stream_state *stream =
+ dm_test_native_mode_pruned_stream(test, DRM_MODE_CONNECTOR_eDP);
+
+ KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 3840);
+ KUNIT_EXPECT_EQ(test, (int)stream->timing.v_addressable, 2400);
+ KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920);
+ KUNIT_EXPECT_EQ(test, (int)stream->src.height, 1200);
+ dc_stream_release(stream);
+}
+
+/**
+ * dm_test_create_stream_native_mode_pruned_lvds - Test LVDS gets native timing
+ * @test: The KUnit test context
+ */
+static void dm_test_create_stream_native_mode_pruned_lvds(struct kunit *test)
+{
+ struct dc_stream_state *stream =
+ dm_test_native_mode_pruned_stream(test, DRM_MODE_CONNECTOR_LVDS);
+
+ KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 3840);
+ KUNIT_EXPECT_EQ(test, (int)stream->timing.v_addressable, 2400);
+ KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920);
+ KUNIT_EXPECT_EQ(test, (int)stream->src.height, 1200);
+ dc_stream_release(stream);
+}
+
+/**
+ * dm_test_create_stream_native_mode_pruned_hdmi - Test HDMI uses the first mode
+ * @test: The KUnit test context
+ *
+ * On a connector that is not a panel, mode_valid may have dropped the native
+ * mode, so the first mode left in the list must drive the timing
+ */
+static void dm_test_create_stream_native_mode_pruned_hdmi(struct kunit *test)
+{
+ struct dc_stream_state *stream =
+ dm_test_native_mode_pruned_stream(test, DRM_MODE_CONNECTOR_HDMIA);
+
+ KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1920);
+ KUNIT_EXPECT_EQ(test, (int)stream->timing.v_addressable, 1200);
+ KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920);
+ KUNIT_EXPECT_EQ(test, (int)stream->src.height, 1200);
+ dc_stream_release(stream);
+}
+
/* Tests for amdgpu_dm_connector_detect() */
/*
@@ -10246,6 +10355,9 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_stream_dp_vsc_colorimetry),
KUNIT_CASE(dm_test_create_stream_dp_colorimetry_quirk),
KUNIT_CASE(dm_test_create_stream_hdmi_vsif),
+ KUNIT_CASE(dm_test_create_stream_native_mode_pruned_edp),
+ KUNIT_CASE(dm_test_create_stream_native_mode_pruned_lvds),
+ KUNIT_CASE(dm_test_create_stream_native_mode_pruned_hdmi),
/* amdgpu_dm_connector_detect */
KUNIT_CASE(dm_test_detect_force_on),
KUNIT_CASE(dm_test_detect_force_on_digital),
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 1/2] drm/amd/display: Fall back to the native mode, not the first, on eDP/LVDS
2026-10-14 22:30 ` [PATCH 1/2] drm/amd/display: Fall back to the native mode, not the first, on eDP/LVDS Hugo Sales
@ 2026-10-23 18:26 ` Chung, ChiaHsuan (Tom)
0 siblings, 0 replies; 4+ messages in thread
From: Chung, ChiaHsuan (Tom) @ 2026-10-23 18:26 UTC (permalink / raw)
To: Hugo Sales, amd-gfx
Cc: dri-devel, linux-kernel, harry.wentland, sunpeng.li, siqueira,
alexander.deucher
On 10/15/2026 6:30 AM, Hugo Sales wrote:
> Currently, create_stream_for_sink() takes the native timing for a scaled
> mode from the preferred mode in connector->modes. After fbdev emulation
> probes with size limits below the native resolution, as it does when
> video= sets a smaller mode, that mode is no longer in the list
>
> If that probe is the last one before a suspend, for example after an
> HDMI hotplug, the resume finds no preferred mode, and the panel is
> driven with the scaled mode's timing instead of its own
>
> amdgpu_dm_get_native_mode() copies the preferred mode to the encoder on
> every probe, before the size limits apply. Fall back to that copy when
> connector->modes has no preferred mode. Limit this to eDP and LVDS,
> where amdgpu turns scaling on by itself. On other connectors mode_valid
> can also leave the preferred mode out, and then the first valid mode is
> the right fallback
>
> Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5983
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Hugo Sales <hugo@hsal.es>
Hi Hugo,
Thanks for the patch and the detailed analysis.
This no longer applies to the current amd-staging-drm-next. It
conflicts with commit 3299935e65c0 ("drm/amd/display: Use separate
iterator for preferred mode lookup"), which landed after the tree you
based this on. That commit removed native_mode_found from
create_stream_for_sink() and now checks preferred_mode directly:
if (!preferred_mode)
preferred_mode = list_first_entry_or_null(...);
Could you rebase the series onto the latest amd-staging-drm-next and
send a v2? The change should be mechanical: test !preferred_mode
instead of !native_mode_found, and drop the native_mode_found = true
assignment. The logic stays the same. Patch 2/2 still applies cleanly.
Also, the dm_test_fs_caps_hdmi_vsdb and dm_test_fs_caps_pcon_whitelist
failures you mentioned in the cover letter were fixed by commit
5ac0b4dd2a1f ("drm/amd/display: Fix FreeSync caps tests after common
EDID VSDB switch"). With the series rebased that way, the whole
amdgpu_dm_connector KUnit suite passes on our side, including your new
tests.
Thanks,
Tom
> ---
> .../display/amdgpu_dm/amdgpu_dm_connector.c | 28 +++++++++++++++++++
> 1 file changed, 28 insertions(+)
>
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> index 428a5f937..a1dc3e287 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> @@ -257,6 +257,8 @@ STATIC_IFN_KUNIT void update_subconnector_property(struct amdgpu_dm_connector *a
> EXPORT_IF_KUNIT(update_subconnector_property);
>
> STATIC_IFN_KUNIT int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
> +STATIC_IFN_KUNIT struct drm_encoder *amdgpu_dm_connector_to_encoder(
> + struct drm_connector *connector);
>
> STATIC_IFN_KUNIT void amdgpu_dm_fbc_init(struct drm_connector *connector)
> {
> @@ -1470,6 +1472,7 @@ create_stream_for_sink(struct drm_connector *connector,
> struct dc_stream_state *stream = NULL;
> struct drm_display_mode mode;
> struct drm_display_mode saved_mode;
> + struct drm_display_mode native_mode;
> struct drm_display_mode *freesync_mode = NULL;
> bool native_mode_found = false;
> bool recalculate_timing = false;
> @@ -1529,6 +1532,31 @@ create_stream_for_sink(struct drm_connector *connector,
> break;
> }
> }
> +
> + /*
> + * After a probe with size limits smaller than the preferred mode,
> + * connector->modes no longer has that mode, and the first mode left
> + * would become the stream timing. fbdev emulation probes with limits
> + * that video= can set below the native size. The encoder has a copy of
> + * the preferred mode from amdgpu_dm_get_native_mode(), made on every
> + * probe before the limits apply, so use that copy. Only do this for eDP
> + * and LVDS panels, which take only their native timing. On other
> + * connectors mode_valid can also reject the preferred mode, and then
> + * the first valid mode is the right one
> + */
> + if (!native_mode_found && aconnector && !aconnector->mst_root && aconnector->drm_edid &&
> + (connector->connector_type == DRM_MODE_CONNECTOR_eDP ||
> + connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
> + struct drm_encoder *encoder = amdgpu_dm_connector_to_encoder(connector);
> +
> + if (encoder && to_amdgpu_encoder(encoder)->native_mode.clock) {
> + drm_mode_copy(&native_mode, &to_amdgpu_encoder(encoder)->native_mode);
> + drm_mode_set_crtcinfo(&native_mode, CRTC_INTERLACE_HALVE_V);
> + preferred_mode = &native_mode;
> + native_mode_found = true;
> + }
> + }
> +
> if (!native_mode_found)
> preferred_mode = list_first_entry_or_null(
> &connector->modes,
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-23 18:26 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-14 22:30 [PATCH 0/2] drm/amd/display: Keep eDP native timing after resume with video= Hugo Sales
2026-10-14 22:30 ` [PATCH 1/2] drm/amd/display: Fall back to the native mode, not the first, on eDP/LVDS Hugo Sales
2026-10-23 18:26 ` Chung, ChiaHsuan (Tom)
2026-10-14 22:30 ` [PATCH 2/2] drm/amd/display: Add KUnit tests for the eDP/LVDS native mode fallback Hugo Sales
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®