Hi,

this series reverts the "consolidate DCN vblank/flip handling onto
vupdate_no_lock" change (c87e6635d2db) and its two follow-ups, which cause
frequent, short bursts of visual corruption on a gfx1201 multi-monitor
setup. The known follow-up fix (70de0a021658, "Atomize IRQ register
read/modify/write ops", which closes #5616) does NOT cover this failure
mode - the corruption there is not accompanied by any flip_done timeouts,
and it remains with the fix applied.

Symptom: brief (~0.1s) bursts of white-noise strips on ALL connected
displays simultaneously, nearly constant within minutes of normal Wayland
desktop use, in a mixed-timing dual-monitor configuration
(3840x2160@144 DP + 1920x1080@60 HDMI). Kernel log is completely quiet at
the events. This is visually distinct from #5616's freezes/flip_done
timeouts - I believe it is an unreported second failure mode of the same
commit.

Evidence (module-level test builds against distro 7.0.0-38 headers,
vermagic- and CRC-identical to stock, only amdgpu.ko/ttm.ko swapped):

  without the commit (older kernel state) .................. clean (>=10 min)   with the commit .......................................... glitchy (~1/6s)
  commit + follow-ups reverted ............................. clean (hours)
  commit kept, only follow-ups reverted .................... glitchy
  commit kept + 70de0a021658 applied ....................... glitchy

Also ruled out: DCC / explicit modifiers (reproduces with
WLR_EGL_NO_MODIFIERS=1 and radeonsi R600_DEBUG=nodcc), DPM clock
transitions (mclk sits pinned at its top level in this configuration and
per-clock-domain transition statistics are identical in glitch and
glitch-free windows), firmware age (linux-firmware HEAD), VRR (off),
display link and engine (TTY console at identical modes is clean), and
hardware (Windows clean for weeks at identical timings).

Full report and raw data: https://gitlab.freedesktop.org/drm/amd/-/issues/6004

I understand a blanket revert may undo intended vblank-timing improvements,
so if you prefer a targeted change (e.g. falling back to the legacy vblank
IRQ source for multi-CRTC / mixed-timing configurations), I can turn that
around quickly and test it: my build+reproducibility loop is minutes, and
the glitch is near-constant when present, so results are decisive within a
5-minute test session.

Thanks,
Ava H
From 4a85e560043fb74aa2fc501a786c220b6f006b92 Mon Sep 17 00:00:00 2001
From: Ava H <[email protected]>
Date: Sun, 18 Oct 2026 11:23:54 -0500
Subject: [PATCH 3/3] drm/amd/display: Revert "consolidate DCN vblank/flip
 handling onto vupdate_no_lock"

This reverts commit c87e6635d2db (Ubuntu stable backport 9ae6be4a6).

Regression: on gfx1201 (RX 9070 XT, DCN 4.0.1) with two monitors at
different timings (3840x2160@144 DP + 1920x1080@60 HDMI) under Wayland
(sway/wlroots), this commit causes brief bursts (~0.1s) of white-noise
strips on ALL displays simultaneously, nearly constant within minutes of
normal desktop use. No kernel log output at the events (no flip_done
timeouts, resets, or page faults).

Evidence (module-level test builds, vermagic/CRC-identical, only
amdgpu.ko/ttm.ko swapped):
  - kernel without this commit (7.0.0-34-era DRM code): clean (>=10 min)
  - kernel with this commit (7.0.0-38): glitchy (~1 per 6s of desktop use)
  - with commit reverted (plus the 2 follow-ups): clean (hours of soak)
  - with commit kept, only follow-ups reverted: glitchy
  - with commit kept + backport of 70de0a021658 (Atomize IRQ register RMW
    ops, Fixes: c87e6635d2db, closes #5616): still glitchy -> the known fix
    covers only the flip_done-timeout failure mode, not this corruption mode

Ruled out: DCC/explicit modifiers (reproduces with WLR_EGL_NO_MODIFIERS=1
and radeonsi R600_DEBUG=nodcc), DPM clock transitions (mclk pinned at top
level in this config; per-domain transition statistics identical in glitch
and glitch-free windows), firmware age, VRR (disabled), display link/engine
(TTY console at identical modes is clean), hardware (Windows clean at
identical timings).

Until the failure mode inside the consolidation is understood, reverting
restores correctness for multi-monitor/mixed-timing users. Happy to test
targeted fixes quickly (build+test turnaround is minutes).

Reported-by: Ava H <[email protected]>
Tested-by: Ava H <[email protected]>
Signed-off-by: Ava H <[email protected]>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 347 ++++++++----------
 .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c    |  70 ++--
 2 files changed, 189 insertions(+), 228 deletions(-)

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 a747eefea..f65132d54 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -572,25 +572,89 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev,
 	queue_work(system_wq, &offload_work->work);
 }
 
+static void dm_vupdate_high_irq(void *interrupt_params)
+{
+	struct common_irq_params *irq_params = interrupt_params;
+	struct amdgpu_device *adev = irq_params->adev;
+	struct amdgpu_crtc *acrtc;
+	struct drm_device *drm_dev;
+	struct drm_vblank_crtc *vblank;
+	ktime_t frame_duration_ns, previous_timestamp;
+	unsigned long flags;
+	int vrr_active;
+
+	acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE);
+
+	if (acrtc) {
+		vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
+		drm_dev = acrtc->base.dev;
+		vblank = drm_crtc_vblank_crtc(&acrtc->base);
+		previous_timestamp = atomic64_read(&irq_params->previous_timestamp);
+		frame_duration_ns = vblank->time - previous_timestamp;
+
+		if (frame_duration_ns > 0) {
+			trace_amdgpu_refresh_rate_track(acrtc->base.index,
+						frame_duration_ns,
+						ktime_divns(NSEC_PER_SEC, frame_duration_ns));
+			atomic64_set(&irq_params->previous_timestamp, vblank->time);
+		}
+
+		drm_dbg_vbl(drm_dev,
+			    "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id,
+			    vrr_active);
+
+		/* Core vblank handling is done here after end of front-porch in
+		 * vrr mode, as vblank timestamping will give valid results
+		 * while now done after front-porch. This will also deliver
+		 * page-flip completion events that have been queued to us
+		 * if a pageflip happened inside front-porch.
+		 */
+		if (vrr_active && acrtc->dm_irq_params.stream) {
+			bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
+			bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
+			bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state
+				== VRR_STATE_ACTIVE_VARIABLE;
+
+			amdgpu_dm_crtc_handle_vblank(acrtc);
+
+			/* BTR processing for pre-DCE12 ASICs */
+			if (adev->family < AMDGPU_FAMILY_AI) {
+				spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+				mod_freesync_handle_v_update(
+				    adev->dm.freesync_module,
+				    acrtc->dm_irq_params.stream,
+				    &acrtc->dm_irq_params.vrr_params);
+
+				if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
+					schedule_dc_vmin_vmax(adev,
+						acrtc->dm_irq_params.stream,
+						&acrtc->dm_irq_params.vrr_params.adjust);
+				}
+				spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+			}
+		}
+	}
+}
+
 /**
- * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling
- * @adev: amdgpu device
- * @acrtc: the CRTC to service
- *
- * Performs writeback completion, vblank event handling, CRC processing, VRR BTR
- * updates and pageflip completion delivery.
+ * dm_crtc_high_irq() - Handles CRTC interrupt
+ * @interrupt_params: used for determining the CRTC instance
  *
- * On DCN this is driven by VUPDATE_NO_LOCK (the register latch point) from
- * dm_vupdate_high_irq(); on DCE it is driven by VLINE0 at the start of vblank
- * from dm_crtc_high_irq().
+ * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK
+ * event handler.
  */
-static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
-				     struct amdgpu_crtc *acrtc)
+static void dm_crtc_high_irq(void *interrupt_params)
 {
+	struct common_irq_params *irq_params = interrupt_params;
+	struct amdgpu_device *adev = irq_params->adev;
 	struct drm_writeback_job *job;
+	struct amdgpu_crtc *acrtc;
 	unsigned long flags;
 	int vrr_active;
-	bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0;
+
+	acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
+	if (!acrtc)
+		return;
 
 	if (acrtc->wb_conn) {
 		spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags);
@@ -627,17 +691,12 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
 		    vrr_active, acrtc->dm_irq_params.active_planes);
 
 	/**
-	 * Core vblank handling.
-	 *
-	 * On DCN this handler runs at VUPDATE_NO_LOCK, the register latch
-	 * point, which is the correct place to timestamp both VRR and non-VRR
-	 * vblanks.
-	 *
-	 * On DCE this handler runs at the start of front-porch, where only
-	 * non-VRR timestamping is valid; VRR vblank is deferred to
-	 * dm_vupdate_high_irq() after end of front-porch.
+	 * Core vblank handling at start of front-porch is only possible
+	 * in non-vrr mode, as only there vblank timestamping will give
+	 * valid results while done in front-porch. Otherwise defer it
+	 * to dm_vupdate_high_irq after end of front-porch.
 	 */
-	if (is_dcn || !vrr_active)
+	if (!vrr_active)
 		amdgpu_dm_crtc_handle_vblank(acrtc);
 
 	/**
@@ -670,16 +729,18 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
 	}
 
 	/*
-	 * Deliver pageflip completion events (DCN only).
-	 *
-	 * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch
-	 * point. Deliver any pending pageflip completion event from here.
+	 * If there aren't any active_planes then DCH HUBP may be clock-gated.
+	 * In that case, pageflip completion interrupts won't fire and pageflip
+	 * completion events won't get delivered. Prevent this by sending
+	 * pending pageflip events from here if a flip is still pending.
 	 *
-	 * NOTE: This can deliver an event for a flip that was armed but not yet
-	 * programmed into HW; that race is closed in a follow-up change by
-	 * checking the programmed flip status.
+	 * If any planes are enabled, use dm_pflip_high_irq() instead, to
+	 * avoid race conditions between flip programming and completion,
+	 * which could cause too early flip completion events.
 	 */
-	if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) {
+	if (adev->family >= AMDGPU_FAMILY_RV &&
+	    acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+	    acrtc->dm_irq_params.active_planes == 0) {
 		if (acrtc->event) {
 			drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
 			acrtc->event = NULL;
@@ -691,104 +752,6 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
 	spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
 }
 
-static void dm_vupdate_high_irq(void *interrupt_params)
-{
-	struct common_irq_params *irq_params = interrupt_params;
-	struct amdgpu_device *adev = irq_params->adev;
-	struct amdgpu_crtc *acrtc;
-	struct drm_device *drm_dev;
-	struct drm_vblank_crtc *vblank;
-	ktime_t frame_duration_ns, previous_timestamp;
-	unsigned long flags;
-	int vrr_active;
-
-	acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE);
-	if (!acrtc)
-		return;
-
-	vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
-	drm_dev = acrtc->base.dev;
-	vblank = drm_crtc_vblank_crtc(&acrtc->base);
-	previous_timestamp = atomic64_read(&irq_params->previous_timestamp);
-	frame_duration_ns = vblank->time - previous_timestamp;
-
-	if (frame_duration_ns > 0) {
-		trace_amdgpu_refresh_rate_track(acrtc->base.index,
-					frame_duration_ns,
-					ktime_divns(NSEC_PER_SEC, frame_duration_ns));
-		atomic64_set(&irq_params->previous_timestamp, vblank->time);
-	}
-
-	drm_dbg_vbl(drm_dev,
-		    "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id,
-		    vrr_active);
-
-	/*
-	 * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver
-	 * vblank and pageflip completion events; VSTARTUP and GRPH_PFLIP are
-	 * not used. Run the full handler here.
-	 */
-	if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
-		dm_crtc_high_irq_handler(adev, acrtc);
-		return;
-	}
-
-	/* DCE only below. */
-
-	/* Core vblank handling is done here after end of front-porch in
-	 * vrr mode, as vblank timestamping will give valid results
-	 * while now done after front-porch. This will also deliver
-	 * page-flip completion events that have been queued to us
-	 * if a pageflip happened inside front-porch.
-	 */
-	if (vrr_active && acrtc->dm_irq_params.stream) {
-		bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
-		bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
-		bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state
-			== VRR_STATE_ACTIVE_VARIABLE;
-
-		amdgpu_dm_crtc_handle_vblank(acrtc);
-
-		/* BTR processing for pre-DCE12 ASICs */
-		if (adev->family < AMDGPU_FAMILY_AI) {
-			spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
-			mod_freesync_handle_v_update(
-				adev->dm.freesync_module,
-				acrtc->dm_irq_params.stream,
-				&acrtc->dm_irq_params.vrr_params);
-
-			if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
-				schedule_dc_vmin_vmax(adev,
-					acrtc->dm_irq_params.stream,
-					&acrtc->dm_irq_params.vrr_params.adjust);
-			}
-			spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
-		}
-	}
-}
-
-/**
- * dm_crtc_high_irq() - Handles CRTC interrupt
- * @interrupt_params: used for determining the CRTC instance
- *
- * Handles the CRTC/VSYNC interrupt by notifying DRM's VBLANK event handler.
- *
- * Used on DCE (VLINE0, set to vblank start). On DCN the equivalent handling is
- * driven by VUPDATE_NO_LOCK in dm_vupdate_high_irq().
- */
-static void dm_crtc_high_irq(void *interrupt_params)
-{
-	struct common_irq_params *irq_params = interrupt_params;
-	struct amdgpu_device *adev = irq_params->adev;
-	struct amdgpu_crtc *acrtc;
-
-	acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
-	if (!acrtc)
-		return;
-
-	dm_crtc_high_irq_handler(adev, acrtc);
-}
-
 #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
 /**
  * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for
@@ -3133,13 +3096,6 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
 			 */
 			if (!dc_interrupt_set(adev->dm.dc, irq_source, enable))
 				drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis");
-
-		} else if (acrtc && state->stream_status[i].plane_count != 0) {
-			/* DCN only needs to toggle VUPDATE_NO_LOCK */
-			rc = amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, enable);
-			if (rc)
-				drm_warn(adev_to_drm(adev), "Failed to %sable vupdate interrupt\n",
-					 enable ? "en" : "dis");
 		}
 	}
 
@@ -4741,6 +4697,38 @@ static int dcn10_register_irq_handlers(struct amdgpu_device *adev)
 	 *    for acknowledging and handling.
 	 */
 
+	/* Use VSTARTUP interrupt */
+	for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP;
+			i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1;
+			i++) {
+		r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq);
+
+		if (r) {
+			drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n");
+			return r;
+		}
+
+		int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+		int_params.irq_source =
+			dc_interrupt_to_irq_source(dc, i, 0);
+
+		if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+			int_params.irq_source  < DC_IRQ_SOURCE_VBLANK1 ||
+			int_params.irq_source  > DC_IRQ_SOURCE_VBLANK6) {
+			drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n");
+			return -EINVAL;
+		}
+
+		c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
+
+		c_irq_params->adev = adev;
+		c_irq_params->irq_src = int_params.irq_source;
+
+		if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+			dm_crtc_high_irq, c_irq_params))
+			return -ENOMEM;
+	}
+
 	/* Use otg vertical line interrupt */
 #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
 	for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) {
@@ -4812,6 +4800,37 @@ static int dcn10_register_irq_handlers(struct amdgpu_device *adev)
 			return -ENOMEM;
 	}
 
+	/* Use GRPH_PFLIP interrupt */
+	for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT;
+			i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1;
+			i++) {
+		r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq);
+		if (r) {
+			drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n");
+			return r;
+		}
+
+		int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+		int_params.irq_source =
+			dc_interrupt_to_irq_source(dc, i, 0);
+
+		if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+			int_params.irq_source  < DC_IRQ_SOURCE_PFLIP_FIRST ||
+			int_params.irq_source  > DC_IRQ_SOURCE_PFLIP_LAST) {
+			drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n");
+			return -EINVAL;
+		}
+
+		c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
+
+		c_irq_params->adev = adev;
+		c_irq_params->irq_src = int_params.irq_source;
+
+		if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+			dm_pflip_high_irq, c_irq_params))
+			return -ENOMEM;
+	}
+
 	/* HPD */
 	r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT,
 			&adev->hpd_irq);
@@ -9381,22 +9400,14 @@ static void manage_dm_interrupts(struct amdgpu_device *adev,
 
 		drm_crtc_vblank_on_config(&acrtc->base,
 					  &config);
-		/*
-		 * Since pflip_high_irq is no longer registered for DCN, grab an
-		 * extra reference to vupdate irq instead to workaround this
-		 * issue:
-		 * https://gitlab.freedesktop.org/drm/amd/-/work_items/3936
-		 *
-		 * The callbacks to drm_vblank_on/off should really take care of
-		 * this though.
-		 */
+		/* Allow RX6xxx, RX7700, RX7800 GPUs to call amdgpu_irq_get.*/
 		switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
 		case IP_VERSION(3, 0, 0):
 		case IP_VERSION(3, 0, 2):
 		case IP_VERSION(3, 0, 3):
 		case IP_VERSION(3, 2, 0):
-			if (amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type))
-				drm_err(dev, "DM_IRQ: Cannot get vupdate irq!\n");
+			if (amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type))
+				drm_err(dev, "DM_IRQ: Cannot get pageflip irq!\n");
 #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
 			if (amdgpu_irq_get(adev, &adev->vline0_irq, irq_type))
 				drm_err(dev, "DM_IRQ: Cannot get vline0 irq!\n");
@@ -9414,8 +9425,8 @@ static void manage_dm_interrupts(struct amdgpu_device *adev,
 			if (amdgpu_irq_put(adev, &adev->vline0_irq, irq_type))
 				drm_err(dev, "DM_IRQ: Cannot put vline0 irq!\n");
 #endif
-			if (amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type))
-				drm_err(dev, "DM_IRQ: Cannot put vupdate irq!\n");
+			if (amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type))
+				drm_err(dev, "DM_IRQ: Cannot put pageflip irq!\n");
 		}
 
 		drm_crtc_vblank_off(&acrtc->base);
@@ -9428,10 +9439,6 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev,
 	int irq_type =
 		amdgpu_display_crtc_idx_to_irq_type(adev, acrtc->crtc_id);
 
-	/* GRPH_PFLIP is not used on DCN; nothing to reapply. */
-	if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0)
-		return;
-
 	/**
 	 * This reads the current state for the IRQ and force reapplies
 	 * the setting to hardware.
@@ -9763,13 +9770,9 @@ static void update_stream_irq_parameters(
 static void amdgpu_dm_handle_vrr_transition(struct dm_crtc_state *old_state,
 					    struct dm_crtc_state *new_state)
 {
-	struct amdgpu_device *adev = drm_to_adev(new_state->base.crtc->dev);
 	bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state);
 	bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state);
 
-	/* Only DCE gates vupdate on VRR, keep it enabled for DCN */
-	bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0;
-
 	if (!old_vrr_active && new_vrr_active) {
 		/* Transition VRR inactive -> active:
 		 * While VRR is active, we must not disable vblank irq, as a
@@ -9779,8 +9782,7 @@ static void amdgpu_dm_handle_vrr_transition(struct dm_crtc_state *old_state,
 		 * We also need vupdate irq for the actual core vblank handling
 		 * at end of vblank.
 		 */
-		if (vrr_gates_vupdate)
-			WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
+		WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
 		WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0);
 		drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n",
 				 __func__, new_state->base.crtc->base.id);
@@ -9788,8 +9790,7 @@ static void amdgpu_dm_handle_vrr_transition(struct dm_crtc_state *old_state,
 		/* Transition VRR active -> inactive:
 		 * Allow vblank irq disable again for fixed refresh rate.
 		 */
-		if (vrr_gates_vupdate)
-			WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
+		WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
 		drm_crtc_vblank_put(new_state->base.crtc);
 		drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n",
 				 __func__, new_state->base.crtc->base.id);
@@ -9964,7 +9965,6 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
 	bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state);
 	bool cursor_update = false;
 	bool pflip_present = false;
-	bool immediate_flip = false;
 	bool dirty_rects_changed = false;
 	bool updated_planes_and_streams = false;
 	struct {
@@ -10130,8 +10130,6 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
 			acrtc_state->update_type == UPDATE_TYPE_FAST &&
 			get_mem_type(old_plane_state->fb) == get_mem_type(fb);
 
-		immediate_flip |= bundle->flip_addrs[planes_count].flip_immediate;
-
 		timestamp_ns = ktime_get_ns();
 		bundle->flip_addrs[planes_count].flip_timestamp_in_us = div_u64(timestamp_ns, 1000);
 		bundle->surface_updates[planes_count].flip_addr = &bundle->flip_addrs[planes_count];
@@ -10333,29 +10331,6 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
 	    acrtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE)
 		amdgpu_dm_commit_cursors(state);
 
-	/*
-	 * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK.
-	 * However, an immediate (tearing / async) flip is latched by HW right
-	 * away and does not wait for the next vupdate, so deliver its
-	 * completion event here after programming.
-	 *
-	 * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so
-	 * this is DCN-only.
-	 */
-	if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) {
-		spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
-		if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED &&
-		    acrtc_attach->event) {
-			drm_crtc_accurate_vblank_count(&acrtc_attach->base);
-			drm_crtc_send_vblank_event(&acrtc_attach->base,
-						   acrtc_attach->event);
-			acrtc_attach->event = NULL;
-			drm_crtc_vblank_put(&acrtc_attach->base);
-			acrtc_attach->pflip_status = AMDGPU_FLIP_NONE;
-		}
-		spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
-	}
-
 cleanup:
 	kfree(bundle);
 }
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
index ad4b724f8..7cfaf225f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
@@ -309,14 +309,7 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
 			drm_crtc_vblank_restore(crtc);
 	}
 
-	/*
-	 * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver
-	 * vblank and pageflip completion events, so enable it whenever vblank
-	 * is enabled. On DCE, vupdate is only needed in VRR mode.
-	 */
-	if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
-		rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable);
-	} else if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
+	if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
 		if (enable) {
 			/* vblank irq on -> Only need vupdate irq in vrr mode */
 			if (amdgpu_dm_crtc_vrr_active(acrtc_state))
@@ -330,44 +323,37 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
 	if (rc)
 		return rc;
 
-	/*
-	 * VLINE0 (crtc_irq) and GRPH_PFLIP (pageflip_irq) are only used on
-	 * DCE. On DCN, vblank and pageflip completion are delivered from
-	 * VUPDATE_NO_LOCK (enabled above), so don't touch them here.
-	 */
-	if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) {
-		/* crtc vblank or vstartup interrupt */
-		if (enable) {
-			rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type);
-			drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc);
-		} else {
-			rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type);
-			drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc);
-		}
-
-		if (rc)
-			return rc;
+	/* crtc vblank or vstartup interrupt */
+	if (enable) {
+		rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type);
+		drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc);
+	} else {
+		rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type);
+		drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc);
+	}
 
-		/*
-		 * hubp surface flip interrupt
-		 *
-		 * We have no guarantee that the frontend index maps to the same
-		 * backend index - some even map to more than one.
-		 *
-		 * TODO: Use a different interrupt or check DC itself for the mapping.
-		 */
-		if (enable) {
-			rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type);
-			drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc);
-		} else {
-			rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type);
-			drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc);
-		}
+	if (rc)
+		return rc;
 
-		if (rc)
-			return rc;
+	/*
+	 * hubp surface flip interrupt
+	 *
+	 * We have no guarantee that the frontend index maps to the same
+	 * backend index - some even map to more than one.
+	 *
+	 * TODO: Use a different interrupt or check DC itself for the mapping.
+	 */
+	if (enable) {
+		rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type);
+		drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc);
+	} else {
+		rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type);
+		drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc);
 	}
 
+	if (rc)
+		return rc;
+
 #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
 	/* crtc vline0 interrupt, only available on DCN+ */
 	if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
-- 
2.53.0

From d7c6afdfb38772a8e7b8c3f5b5171f6c6cfcbcff Mon Sep 17 00:00:00 2001
From: Ava H <[email protected]>
Date: Sun, 18 Oct 2026 11:23:54 -0500
Subject: [PATCH 2/3] drm/amd/display: revert "Fix flip-done timeouts on mode1
 reset"

This reverts commit 172795a98, part of the vupdate_no_lock vblank handling
series. See the final commit of the series for the regression evidence.

Signed-off-by: Ava H <[email protected]>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c  | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
index ec7939cb0..ad4b724f8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
@@ -315,19 +315,10 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
 	 * is enabled. On DCE, vupdate is only needed in VRR mode.
 	 */
 	if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
-		if (enable) {
-			rc = amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type);
-			drm_dbg_vbl(crtc->dev, "Get vupdate_irq ret=%d\n", rc);
-		} else {
-			rc = amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type);
-			drm_dbg_vbl(crtc->dev, "Put vupdate_irq ret=%d\n", rc);
-		}
+		rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable);
 	} else if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
 		if (enable) {
-			/* vblank irq on -> Only need vupdate irq in vrr mode
-			 * Not ref-counted since we need explicit enable/disable
-			 * for DCE VRR handling
-			 */
+			/* vblank irq on -> Only need vupdate irq in vrr mode */
 			if (amdgpu_dm_crtc_vrr_active(acrtc_state))
 				rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, true);
 		} else {
-- 
2.53.0

From 8f5e1439b1e1b93dd9398d59b2f2d20a3ff868e1 Mon Sep 17 00:00:00 2001
From: Ava H <[email protected]>
Date: Sun, 18 Oct 2026 11:23:53 -0500
Subject: [PATCH 1/3] drm/amd/display: revert "Fix missing DCE check in
 dm_gpureset_toggle_interrupts()"

This reverts commit 06586d3bf, part of the vupdate_no_lock vblank handling
series. Reverted as a unit with the consolidation it follows up on.

Reason: visual corruption regression, see the final commit of the series.

Signed-off-by: Ava H <[email protected]>
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

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 69a14bcf6..a747eefea 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -3105,8 +3105,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
 		acrtc = get_crtc_by_otg_inst(
 				adev, state->stream_status[i].primary_otg_inst);
 
-		if (acrtc && state->stream_status[i].plane_count != 0 &&
-		    amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) {
+		if (acrtc && state->stream_status[i].plane_count != 0) {
 			irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst;
 			rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY;
 			if (rc)
-- 
2.53.0

Reply via email to