mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] drm/amd/display: Take every stream out of self-refresh before a modeset
@ 2026-10-29 16:54 Hugo Sales
  2026-11-04 10:03 ` Ray Wu
  0 siblings, 1 reply; 2+ messages in thread
From: Hugo Sales @ 2026-10-29 16:54 UTC (permalink / raw)
  To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher
  Cc: Ray Wu, Chenyu Chen, Hersen Wu, Roman Li, Alex Hung,
	Christian König, David Airlie, Simona Vetter, amd-gfx,
	dri-devel, linux-kernel

Commit 3c108046e1d6 ("drm/amd/display: Add power module on Linux")
dropped amdgpu_dm_psr_disable_all() and amdgpu_dm_replay_disable_all()
from amdgpu_dm_commit_streams(). Since then, a modeset only disables
self-refresh on the CRTCs in the commit, so an eDP on another CRTC stays
in PSR through dc_commit_streams()

Commit 6ee90e88b976 ("drm/amd/display: PSR eDP p-state warning occurs
intermittently after unplug DP") added that disable-all for this very
case. With it gone, turning off or unplugging an HDMI monitor while the
eDP is in PSR hangs DMUB on a Cezanne laptop (DCN 2.1), on the first
try:

  dc_dmub_srv_log_diagnostic_data: DMCUB error
  Error queueing DMUB command: status=2

The eDP then stays frozen until a GPU reset

Do the same through the power module's events. When a CRTC that is or
was active needs a modeset, raise the PSR and Replay pause events on
every stream mod_power tracks and wait for self-refresh to exit, before
amdgpu_dm_mod_power_update_streams() changes the stream set. Clear them
on the commit's streams after amdgpu_dm_mod_power_setup_streams(), since
enabling a stream clears psr_feature_enabled until then. Use
psr_event_pause and replay_event_pause rather than the hw_programming
events, which a fast update on the eDP can clear in
amdgpu_dm_enable_self_refresh() while the modeset is still running

Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux")
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Tested on an ASUS ROG Flow X13 (Cezanne, DMUB 0x0101002B) with PSR 1 on
the eDP. That's the only hardware I have, and PSR there needs
DC_PSR_MASK and "drm/amd/display: Don't require DMUB PSR caps before
DCN 3.0", which I'm sending next. With this patch, 50 HDMI unplugs in a
row with the eDP in PSR went through without a DMUB error. The same
kernel without it hung on the first

Ray, Leo: 3c108046e1d6 doesn't say whether leaving the other CRTCs in
self-refresh was intended. If newer DMUB firmware copes with an external
link going away during PSR, I can limit this to DCN 2.1

 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 101 ++++++++++++++++++
 1 file changed, 101 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index dfbc384fbbdb4..54eee9e2393fa 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -4443,6 +4443,105 @@ STATIC_IFN_KUNIT void dm_clear_writeback(struct amdgpu_display_manager *dm,
 }
 EXPORT_IF_KUNIT(dm_clear_writeback);
 
+static bool amdgpu_dm_self_refresh_pause_needed(struct drm_atomic_commit *state)
+{
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	struct drm_crtc *crtc;
+	int i;
+
+	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i)
+		if (drm_atomic_crtc_needs_modeset(new_crtc_state) &&
+		    (old_crtc_state->active || new_crtc_state->active))
+			return true;
+
+	return false;
+}
+
+static bool amdgpu_dm_is_new_modeset_stream(struct drm_atomic_commit *state,
+					    const struct dc_stream_state *stream)
+{
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_crtc *crtc;
+	int i;
+
+	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i)
+		if (drm_atomic_crtc_needs_modeset(new_crtc_state) &&
+		    to_dm_crtc_state(new_crtc_state)->stream == stream)
+			return true;
+
+	return false;
+}
+
+/*
+ * A modeset CRTC may have no PSR or Replay, such as an HDMI output being
+ * turned off, while an eDP on another CRTC is still in self-refresh. Pause
+ * self-refresh on every stream mod_power tracks before
+ * amdgpu_dm_mod_power_update_streams() changes the set: the old streams of
+ * the CRTCs in the commit and the unchanged streams of the new state.
+ * Unlike hw_programming, which a fast update on another CRTC clears, the
+ * pause holds until amdgpu_dm_resume_self_refresh()
+ */
+static void amdgpu_dm_pause_self_refresh(struct drm_atomic_commit *state,
+					 struct amdgpu_display_manager *dm,
+					 struct dc_state *dc_state)
+{
+	struct drm_crtc_state *old_crtc_state;
+	struct dc_stream_state *stream;
+	struct drm_crtc *crtc;
+	int i;
+
+	if (!amdgpu_dm_self_refresh_pause_needed(state))
+		return;
+
+	guard(mutex)(&dm->dc_lock);
+	dc_exit_ips_for_hw_access(dm->dc);
+
+	for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) {
+		stream = to_dm_crtc_state(old_crtc_state)->stream;
+		if (!old_crtc_state->active || !stream)
+			continue;
+
+		amdgpu_dm_psr_set_event(dm, stream, true, psr_event_pause, true);
+		amdgpu_dm_replay_set_event(dm, stream, true, replay_event_pause, true);
+	}
+
+	for (i = 0; i < dc_state->stream_count; i++) {
+		stream = dc_state->streams[i];
+		if (amdgpu_dm_is_new_modeset_stream(state, stream))
+			continue;
+
+		amdgpu_dm_psr_set_event(dm, stream, true, psr_event_pause, true);
+		amdgpu_dm_replay_set_event(dm, stream, true, replay_event_pause, true);
+	}
+}
+
+/*
+ * Ends amdgpu_dm_pause_self_refresh() on the streams of the commit, which
+ * mod_power tracks from amdgpu_dm_mod_power_update_streams() on, even if
+ * dc_commit_streams() failed. mod_power keeps events across
+ * mod_power_replace_stream(), so the replaced streams carry the pause too.
+ * Runs after amdgpu_dm_mod_power_setup_streams(), since enabling a stream
+ * clears psr_feature_enabled and replay_feature_enabled on its link until
+ * then, and the set_event helpers skip a link without them
+ */
+static void amdgpu_dm_resume_self_refresh(struct drm_atomic_commit *state,
+					  struct amdgpu_display_manager *dm,
+					  struct dc_state *dc_state)
+{
+	int i;
+
+	if (!dc_state || !amdgpu_dm_self_refresh_pause_needed(state))
+		return;
+
+	guard(mutex)(&dm->dc_lock);
+	for (i = 0; i < dc_state->stream_count; i++) {
+		amdgpu_dm_psr_set_event(dm, dc_state->streams[i], false,
+					psr_event_pause, false);
+		amdgpu_dm_replay_set_event(dm, dc_state->streams[i], false,
+					   replay_event_pause, false);
+	}
+}
+
 /**
  * amdgpu_dm_mod_power_update_streams - update mod_power stream state
  * @state: the drm atomic state
@@ -4627,6 +4726,7 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state,
 		acrtc->wb_enabled = false;
 	}
 
+	amdgpu_dm_pause_self_refresh(state, dm, dc_state);
 	amdgpu_dm_mod_power_update_streams(state, dm);
 
 	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
@@ -5370,6 +5470,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state)
 	}
 
 	amdgpu_dm_mod_power_setup_streams(state, dm);
+	amdgpu_dm_resume_self_refresh(state, dm, dc_state);
 
 	for_each_new_crtc_in_state(state, crtc, new_crtc_state, j)
 		if (new_crtc_state->async_flip)

base-commit: ba496bb837882eb1af58f41572fc289e46c77b8c
-- 
2.56.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH] drm/amd/display: Take every stream out of self-refresh before a modeset
  2026-10-29 16:54 [PATCH] drm/amd/display: Take every stream out of self-refresh before a modeset Hugo Sales
@ 2026-11-04 10:03 ` Ray Wu
  0 siblings, 0 replies; 2+ messages in thread
From: Ray Wu @ 2026-11-04 10:03 UTC (permalink / raw)
  To: Hugo Sales, Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher
  Cc: Chenyu Chen, Hersen Wu, Roman Li, Alex Hung,
	Christian König, David Airlie, Simona Vetter, amd-gfx,
	dri-devel, linux-kernel

Reviewed-by: Ray Wu <ray.wu@amd.com>

On 10/30/2026 12:54 AM, Hugo Sales wrote:
> Commit 3c108046e1d6 ("drm/amd/display: Add power module on Linux")
> dropped amdgpu_dm_psr_disable_all() and amdgpu_dm_replay_disable_all()
> from amdgpu_dm_commit_streams(). Since then, a modeset only disables
> self-refresh on the CRTCs in the commit, so an eDP on another CRTC stays
> in PSR through dc_commit_streams()
>
> Commit 6ee90e88b976 ("drm/amd/display: PSR eDP p-state warning occurs
> intermittently after unplug DP") added that disable-all for this very
> case. With it gone, turning off or unplugging an HDMI monitor while the
> eDP is in PSR hangs DMUB on a Cezanne laptop (DCN 2.1), on the first
> try:
>
>    dc_dmub_srv_log_diagnostic_data: DMCUB error
>    Error queueing DMUB command: status=2
>
> The eDP then stays frozen until a GPU reset
>
> Do the same through the power module's events. When a CRTC that is or
> was active needs a modeset, raise the PSR and Replay pause events on
> every stream mod_power tracks and wait for self-refresh to exit, before
> amdgpu_dm_mod_power_update_streams() changes the stream set. Clear them
> on the commit's streams after amdgpu_dm_mod_power_setup_streams(), since
> enabling a stream clears psr_feature_enabled until then. Use
> psr_event_pause and replay_event_pause rather than the hw_programming
> events, which a fast update on the eDP can clear in
> amdgpu_dm_enable_self_refresh() while the modeset is still running
>
> Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux")
> Signed-off-by: Hugo Sales <hugo@hsal.es>
> ---
> Tested on an ASUS ROG Flow X13 (Cezanne, DMUB 0x0101002B) with PSR 1 on
> the eDP. That's the only hardware I have, and PSR there needs
> DC_PSR_MASK and "drm/amd/display: Don't require DMUB PSR caps before
> DCN 3.0", which I'm sending next. With this patch, 50 HDMI unplugs in a
> row with the eDP in PSR went through without a DMUB error. The same
> kernel without it hung on the first
>
> Ray, Leo: 3c108046e1d6 doesn't say whether leaving the other CRTCs in
> self-refresh was intended. If newer DMUB firmware copes with an external
> link going away during PSR, I can limit this to DCN 2.1
>
>   .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 101 ++++++++++++++++++
>   1 file changed, 101 insertions(+)
>
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> index dfbc384fbbdb4..54eee9e2393fa 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
> @@ -4443,6 +4443,105 @@ STATIC_IFN_KUNIT void dm_clear_writeback(struct amdgpu_display_manager *dm,
>   }
>   EXPORT_IF_KUNIT(dm_clear_writeback);
>   
> +static bool amdgpu_dm_self_refresh_pause_needed(struct drm_atomic_commit *state)
> +{
> +	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
> +	struct drm_crtc *crtc;
> +	int i;
> +
> +	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i)
> +		if (drm_atomic_crtc_needs_modeset(new_crtc_state) &&
> +		    (old_crtc_state->active || new_crtc_state->active))
> +			return true;
> +
> +	return false;
> +}
> +
> +static bool amdgpu_dm_is_new_modeset_stream(struct drm_atomic_commit *state,
> +					    const struct dc_stream_state *stream)
> +{
> +	struct drm_crtc_state *new_crtc_state;
> +	struct drm_crtc *crtc;
> +	int i;
> +
> +	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i)
> +		if (drm_atomic_crtc_needs_modeset(new_crtc_state) &&
> +		    to_dm_crtc_state(new_crtc_state)->stream == stream)
> +			return true;
> +
> +	return false;
> +}
> +
> +/*
> + * A modeset CRTC may have no PSR or Replay, such as an HDMI output being
> + * turned off, while an eDP on another CRTC is still in self-refresh. Pause
> + * self-refresh on every stream mod_power tracks before
> + * amdgpu_dm_mod_power_update_streams() changes the set: the old streams of
> + * the CRTCs in the commit and the unchanged streams of the new state.
> + * Unlike hw_programming, which a fast update on another CRTC clears, the
> + * pause holds until amdgpu_dm_resume_self_refresh()
> + */
> +static void amdgpu_dm_pause_self_refresh(struct drm_atomic_commit *state,
> +					 struct amdgpu_display_manager *dm,
> +					 struct dc_state *dc_state)
> +{
> +	struct drm_crtc_state *old_crtc_state;
> +	struct dc_stream_state *stream;
> +	struct drm_crtc *crtc;
> +	int i;
> +
> +	if (!amdgpu_dm_self_refresh_pause_needed(state))
> +		return;
> +
> +	guard(mutex)(&dm->dc_lock);
> +	dc_exit_ips_for_hw_access(dm->dc);
> +
> +	for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) {
> +		stream = to_dm_crtc_state(old_crtc_state)->stream;
> +		if (!old_crtc_state->active || !stream)
> +			continue;
> +
> +		amdgpu_dm_psr_set_event(dm, stream, true, psr_event_pause, true);
> +		amdgpu_dm_replay_set_event(dm, stream, true, replay_event_pause, true);
> +	}
> +
> +	for (i = 0; i < dc_state->stream_count; i++) {
> +		stream = dc_state->streams[i];
> +		if (amdgpu_dm_is_new_modeset_stream(state, stream))
> +			continue;
> +
> +		amdgpu_dm_psr_set_event(dm, stream, true, psr_event_pause, true);
> +		amdgpu_dm_replay_set_event(dm, stream, true, replay_event_pause, true);
> +	}
> +}
> +
> +/*
> + * Ends amdgpu_dm_pause_self_refresh() on the streams of the commit, which
> + * mod_power tracks from amdgpu_dm_mod_power_update_streams() on, even if
> + * dc_commit_streams() failed. mod_power keeps events across
> + * mod_power_replace_stream(), so the replaced streams carry the pause too.
> + * Runs after amdgpu_dm_mod_power_setup_streams(), since enabling a stream
> + * clears psr_feature_enabled and replay_feature_enabled on its link until
> + * then, and the set_event helpers skip a link without them
> + */
> +static void amdgpu_dm_resume_self_refresh(struct drm_atomic_commit *state,
> +					  struct amdgpu_display_manager *dm,
> +					  struct dc_state *dc_state)
> +{
> +	int i;
> +
> +	if (!dc_state || !amdgpu_dm_self_refresh_pause_needed(state))
> +		return;
> +
> +	guard(mutex)(&dm->dc_lock);
> +	for (i = 0; i < dc_state->stream_count; i++) {
> +		amdgpu_dm_psr_set_event(dm, dc_state->streams[i], false,
> +					psr_event_pause, false);
> +		amdgpu_dm_replay_set_event(dm, dc_state->streams[i], false,
> +					   replay_event_pause, false);
> +	}
> +}
> +
>   /**
>    * amdgpu_dm_mod_power_update_streams - update mod_power stream state
>    * @state: the drm atomic state
> @@ -4627,6 +4726,7 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state,
>   		acrtc->wb_enabled = false;
>   	}
>   
> +	amdgpu_dm_pause_self_refresh(state, dm, dc_state);
>   	amdgpu_dm_mod_power_update_streams(state, dm);
>   
>   	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
> @@ -5370,6 +5470,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state)
>   	}
>   
>   	amdgpu_dm_mod_power_setup_streams(state, dm);
> +	amdgpu_dm_resume_self_refresh(state, dm, dc_state);
>   
>   	for_each_new_crtc_in_state(state, crtc, new_crtc_state, j)
>   		if (new_crtc_state->async_flip)
>
> base-commit: ba496bb837882eb1af58f41572fc289e46c77b8c

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-04 10:03 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29 16:54 [PATCH] drm/amd/display: Take every stream out of self-refresh before a modeset Hugo Sales
2026-11-04 10:03 ` Ray Wu

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®