--- /dev/null
+From 8382cd234981ae36299bb66a10bac2cd8ff1b99d Mon Sep 17 00:00:00 2001
+From: Leo Li <sunpeng.li@amd.com>
+Date: Fri, 12 Jun 2026 13:29:31 -0400
+Subject: drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock
+
+From: Leo Li <sunpeng.li@amd.com>
+
+commit 8382cd234981ae36299bb66a10bac2cd8ff1b99d upstream.
+
+[Why]
+
+On DCN, vblank events were delivered from VSTARTUP/VUPDATE
+(dm_crtc_high_irq/dm_vupdate_high_irq) and pageflip completion from
+GRPH_PFLIP (dm_pflip_high_irq). These signals can be masked by hardware
+by a few things:
+
+* DPG - DCN can Dynamically Power Gate parts of the display pipe when a
+ self-refresh capable eDP is connected. DPG is engaged when there's
+ enough static frames (detected through drm_vblank_off). Once gated,
+ even though the OTG (output timing generator) is still enabled,
+ VSTARTUP and GRPH_FLIP are masked.
+
+* GSL - Driver can use the Global Sync Lock to block HW from latching
+ onto double-buffered registers during programming, to prevent HW from
+ latching onto a partially programmed state. This will mask VSTARTUP,
+ GRPH_FLIP, and VUPDATE. See dcn20_pipe_control_lock().
+
+* MALL - A DCN accessible cache introduced in DCN32+ DGPUs that can
+ store fb data to allow for longer DRAM sleep. When scanning out from
+ MALL, VSTARTUP is masked.
+
+When masked, events are never delivered, which can show up as flip_done
+timeouts in the wild.
+
+However, there is an interrupt source on DCN that is never masked:
+VUPDATE_NO_LOCK. It's simply an unmasked variant of VUPDATE, which fires
+while the OTG is active, at the exact point hardware latches
+double-buffered registers. It is therefore the natural single signal for
+delivering both vblank and flip-completion events on DCN, and the
+correct point to timestamp both VRR and non-VRR vblanks.
+
+DCE's interrupt sources are different, it does not have an unmaskable
+VUPDATE_NO_LOCK. The only unmaskable DCE interrupt is VLINE0, but it can
+only be programmed as a vline offset from vsync_start, making it
+unsuitable for VRR. Thus, we keep DCE untouched and use the existing mix
+of interrupt sources.
+
+[How]
+
+For DCN1 and newer only:
+
+* Factor the body of dm_crtc_high_irq() into dm_crtc_high_irq_handler()
+ and drive it from dm_vupdate_high_irq() (VUPDATE_NO_LOCK). DCE keeps
+ using dm_crtc_high_irq() (VSTARTUP) and dm_pflip_high_irq()
+ (GRPH_PFLIP) unchanged.
+
+* Stop registering VSTARTUP (crtc_irq) and GRPH_PFLIP (pageflip_irq) on
+ DCN, and stop enabling them in amdgpu_dm_crtc_set_vblank() /
+ manage_dm_interrupts(). Enable VUPDATE whenever vblank is enabled on
+ DCN (previously only in VRR mode). The secure-display vline0 interrupt
+ is left untouched.
+
+* VUPDATE_NO_LOCK does not early-fire on an immediate (tearing / async)
+ flip, since HW latches the new address right away. Deliver the flip
+ completion event immediately after programming such flips in
+ amdgpu_dm_commit_planes(), and clear pflip_status so the next vupdate
+ handler does not double-send.
+
+v2: Do not gate VUPDATE_NO_LOCK on DCN in dm_handle_vrr_transition()
+ Also toggle VUPDATE_NO_LOCK on DCN in dm_gpureset_toggle_interrupts()
+ Re-cook vblank event count and timestamp for immediate flips
+
+Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations")
+Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787
+Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141
+Assisted-by: Copilot:claude-opus-4.8
+Co-developed-by: Matthew Schwartz <matthew.schwartz@linux.dev>
+Signed-off-by: Matthew Schwartz <matthew.schwartz@linux.dev>
+Tested-by: Mario Limonciello (AMD) <superm1@kernel.org>
+Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
+Signed-off-by: Leo Li <sunpeng.li@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit c87e6635d2db02c88ae8d09529362da672d34770)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 349 +++++++++--------
+ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 70 ++-
+ 2 files changed, 229 insertions(+), 190 deletions(-)
+
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+@@ -575,89 +575,25 @@ static void schedule_dc_vmin_vmax(struct
+ 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() - Handles CRTC interrupt
+- * @interrupt_params: used for determining the CRTC instance
++ * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling
++ * @adev: amdgpu device
++ * @acrtc: the CRTC to service
+ *
+- * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK
+- * event handler.
++ * Performs writeback completion, vblank event handling, CRC processing, VRR BTR
++ * updates and pageflip completion delivery.
++ *
++ * 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().
+ */
+-static void dm_crtc_high_irq(void *interrupt_params)
++static void dm_crtc_high_irq_handler(struct amdgpu_device *adev,
++ struct amdgpu_crtc *acrtc)
+ {
+- 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;
+-
+- acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
+- if (!acrtc)
+- return;
++ bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0;
+
+ if (acrtc->wb_conn) {
+ spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags);
+@@ -694,12 +630,17 @@ static void dm_crtc_high_irq(void *inter
+ vrr_active, acrtc->dm_irq_params.active_planes);
+
+ /**
+- * 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.
++ * 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.
+ */
+- if (!vrr_active)
++ if (is_dcn || !vrr_active)
+ amdgpu_dm_crtc_handle_vblank(acrtc);
+
+ /**
+@@ -732,18 +673,16 @@ static void dm_crtc_high_irq(void *inter
+ }
+
+ /*
+- * 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.
++ * Deliver pageflip completion events (DCN only).
+ *
+- * 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 (adev->family >= AMDGPU_FAMILY_RV &&
+- acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+- acrtc->dm_irq_params.active_planes == 0) {
++ * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch
++ * point. Deliver any pending pageflip completion event from here.
++ *
++ * 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 (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) {
+ if (acrtc->event) {
+ drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
+ acrtc->event = NULL;
+@@ -755,6 +694,104 @@ static void dm_crtc_high_irq(void *inter
+ 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
+@@ -3059,6 +3096,13 @@ static void dm_gpureset_toggle_interrupt
+ */
+ 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");
+ }
+ }
+
+@@ -4558,38 +4602,6 @@ static int dcn10_register_irq_handlers(s
+ * 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++) {
+@@ -4661,37 +4673,6 @@ static int dcn10_register_irq_handlers(s
+ 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);
+@@ -9118,14 +9099,22 @@ static void manage_dm_interrupts(struct
+
+ drm_crtc_vblank_on_config(&acrtc->base,
+ &config);
+- /* Allow RX6xxx, RX7700, RX7800 GPUs to call amdgpu_irq_get.*/
++ /*
++ * 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.
++ */
+ 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->pageflip_irq, irq_type))
+- drm_err(dev, "DM_IRQ: Cannot get pageflip irq!\n");
++ if (amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type))
++ drm_err(dev, "DM_IRQ: Cannot get vupdate 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");
+@@ -9143,8 +9132,8 @@ static void manage_dm_interrupts(struct
+ 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->pageflip_irq, irq_type))
+- drm_err(dev, "DM_IRQ: Cannot put pageflip irq!\n");
++ if (amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type))
++ drm_err(dev, "DM_IRQ: Cannot put vupdate irq!\n");
+ }
+
+ drm_crtc_vblank_off(&acrtc->base);
+@@ -9157,6 +9146,10 @@ static void dm_update_pflip_irq_state(st
+ 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.
+@@ -9488,9 +9481,13 @@ 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
+@@ -9500,7 +9497,8 @@ static void amdgpu_dm_handle_vrr_transit
+ * We also need vupdate irq for the actual core vblank handling
+ * at end of vblank.
+ */
+- WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
++ if (vrr_gates_vupdate)
++ 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);
+@@ -9508,7 +9506,8 @@ static void amdgpu_dm_handle_vrr_transit
+ /* Transition VRR active -> inactive:
+ * Allow vblank irq disable again for fixed refresh rate.
+ */
+- WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
++ if (vrr_gates_vupdate)
++ 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);
+@@ -9683,6 +9682,7 @@ static void amdgpu_dm_commit_planes(stru
+ 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 {
+@@ -9848,6 +9848,8 @@ static void amdgpu_dm_commit_planes(stru
+ 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];
+@@ -10049,6 +10051,29 @@ static void amdgpu_dm_commit_planes(stru
+ 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);
+ }
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+@@ -327,7 +327,14 @@ static inline int amdgpu_dm_crtc_set_vbl
+ drm_crtc_vblank_restore(crtc);
+ }
+
+- if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
++ /*
++ * 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 (enable) {
+ /* vblank irq on -> Only need vupdate irq in vrr mode */
+ if (amdgpu_dm_crtc_vrr_active(acrtc_state))
+@@ -341,36 +348,43 @@ static inline int amdgpu_dm_crtc_set_vbl
+ 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);
+- }
+-
+- 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.
++ * 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 (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 (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;
++ 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+ */
--- /dev/null
+From d340cba0df4cf327c7e89c7c1a4e79d4771d7dd5 Mon Sep 17 00:00:00 2001
+From: Andriy Korud <a.korud@gmail.com>
+Date: Fri, 10 Jul 2026 12:52:26 +0200
+Subject: drm/amd/display: dce100: skip non-DP stream encoders for DP MST
+
+From: Andriy Korud <a.korud@gmail.com>
+
+commit d340cba0df4cf327c7e89c7c1a4e79d4771d7dd5 upstream.
+
+On DCE8-class ASICs (e.g. Bonaire), the resource pool contains digital
+DIG stream encoders plus one analog DAC encoder. When assigning a stream
+encoder for a second DisplayPort MST stream, if the preferred digital
+encoder is already acquired, dce100_find_first_free_match_stream_enc_for_link()
+falls back to the first free pool entry. That entry may be the analog
+encoder, whose funcs table lacks DP hooks such as dp_set_stream_attribute.
+The subsequent atomic commit then dereferences NULL function pointers in
+link_set_dpms_on() and crashes.
+
+Skip encoders without dp_set_stream_attribute when the stream uses a DP
+signal (including MST). Use dc_is_dp_signal(stream->signal) for the MST
+fallback path instead of checking only the link connector signal.
+
+Tested on:
+- GPU: AMD Radeon R7 260X (Bonaire / DCE8)
+- Board: Supermicro C9X299-PG300
+- Setup: DP MST daisy chain, hotplug second monitor or have it connected on boot
+- Kernel: 7.1.3 (issue observed since 6.19)
+- Result: kernel oops without patch; dual monitors stable with patch
+
+Signed-off-by: Andriy Korud <a.korud@gmail.com>
+Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5162
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit 28ec64943e3ee4d9b8d30cea61e380f1429953a8)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+--- a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c
++++ b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c
+@@ -957,6 +957,11 @@ struct stream_encoder *dce100_find_first
+ for (i = 0; i < pool->stream_enc_count; i++) {
+ if (!res_ctx->is_stream_enc_acquired[i] &&
+ pool->stream_enc[i]) {
++ /* DP/MST needs a digital encoder; skip analog/no-DP encoders */
++ if (dc_is_dp_signal(stream->signal) &&
++ (!pool->stream_enc[i]->funcs ||
++ !pool->stream_enc[i]->funcs->dp_set_stream_attribute))
++ continue;
+ /* Store first available for MST second display
+ * in daisy chain use case
+ */
+@@ -980,7 +985,7 @@ struct stream_encoder *dce100_find_first
+ * required for non DP connectors.
+ */
+
+- if (j >= 0 && link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT)
++ if (j >= 0 && dc_is_dp_signal(stream->signal))
+ return pool->stream_enc[j];
+
+ return NULL;
--- /dev/null
+From f1b5d8f9cc54ae8a2567ac126867ae488e1bf625 Mon Sep 17 00:00:00 2001
+From: Mario Limonciello <mario.limonciello@amd.com>
+Date: Mon, 29 Jun 2026 15:27:00 -0500
+Subject: drm/amd/display: Fix backlight max_brightness to match exported range
+
+From: Mario Limonciello <mario.limonciello@amd.com>
+
+commit f1b5d8f9cc54ae8a2567ac126867ae488e1bf625 upstream.
+
+[Why]
+FWTS autobrightness fails on eDP panels because actual_brightness can
+read higher than the advertised max_brightness (e.g. 63576 vs 62451).
+
+The conversion helpers expose the firmware PWM range to userspace as
+[0..max]. But max_brightness is advertised as (max - min), which is
+smaller. So reading the level can return a value above max_brightness.
+
+This regressed in commit 4b61b8a39051 ("drm/amd/display: Add debugging
+message for brightness caps"), which changed max_brightness to
+(max - min) and undid commit 8dbd72cb7900 ("drm/amd/display: Export full
+brightness range to userspace").
+
+[How]
+Advertise max_brightness as max, and scale the initial AC/DC brightness
+against max too. Update the KUnit expectations to match.
+
+Fixes: 4b61b8a39051 ("drm/amd/display: Add debugging message for brightness caps")
+Reviewed-by: Alex Hung <alex.hung@amd.com>
+Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
+Signed-off-by: George Zhang <george.zhang@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit bd9e2b5b0473c75abc0f4134dfe79ecbfb16610d)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+@@ -5204,11 +5204,11 @@ amdgpu_dm_register_backlight_device(stru
+ caps = &dm->backlight_caps[aconnector->bl_idx];
+ if (get_brightness_range(caps, &min, &max)) {
+ if (power_supply_is_system_supplied() > 0)
+- props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100);
++ props.brightness = DIV_ROUND_CLOSEST(max * caps->ac_level, 100);
+ else
+- props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100);
++ props.brightness = DIV_ROUND_CLOSEST(max * caps->dc_level, 100);
+ /* min is zero, so max needs to be adjusted */
+- props.max_brightness = max - min;
++ props.max_brightness = max;
+ drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max,
+ caps->ac_level, caps->dc_level);
+ } else
--- /dev/null
+From bad177fa75e607e396cd57daaaed881450d7a471 Mon Sep 17 00:00:00 2001
+From: Alessandro Rinaldi <ale@alerinaldi.it>
+Date: Fri, 26 Jun 2026 16:36:00 +0200
+Subject: drm/amd/display: Force PWM backlight on Lenovo Legion 5 15ARH05
+
+From: Alessandro Rinaldi <ale@alerinaldi.it>
+
+commit bad177fa75e607e396cd57daaaed881450d7a471 upstream.
+
+The Lenovo Legion 5 15ARH05 (Renoir) ships a BOE 0x08DF eDP panel that
+advertises AUX/DPCD backlight control, so amdgpu's automatic detection
+(amdgpu_backlight == -1) selects AUX. On this panel the AUX backlight
+path has no effect: brightness writes are accepted but the panel level
+never changes, the display is stuck at a fixed brightness and
+max_brightness is reported as a bogus 511000. As a result neither the
+desktop brightness slider nor the brightness hotkeys do anything.
+
+Forcing PWM backlight (amdgpu.backlight=0) restores working control:
+max_brightness becomes 65535 and the level tracks writes. This has long
+been applied by users as a manual kernel-parameter workaround.
+
+Extend the generic panel backlight quirk with a force_pwm flag, add an
+entry for the Legion 5 15ARH05 / BOE 0x08DF panel, and have amdgpu
+disable AUX backlight (use PWM) when the quirk matches and the user
+lets the driver auto-select the backlight type.
+
+Signed-off-by: Alessandro Rinaldi <ale@alerinaldi.it>
+Tested-by: Alessandro Rinaldi <ale@alerinaldi.it>
+Reviewed-by: George Zhang <george.zhang@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit 81b39f43e7e53589491e2eef6bad5389626b4b9c)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 +++++--
+ drivers/gpu/drm/drm_panel_backlight_quirks.c | 9 +++++++++
+ include/drm/drm_utils.h | 1 +
+ 3 files changed, 15 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+@@ -3724,6 +3724,8 @@ static void update_connector_ext_caps(st
+ caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps;
+ caps->aux_support = false;
+
++ panel_backlight_quirk = drm_get_panel_backlight_quirk(aconnector->drm_edid);
++
+ if (caps->ext_caps->bits.oled == 1
+ /*
+ * ||
+@@ -3736,6 +3738,9 @@ static void update_connector_ext_caps(st
+ caps->aux_support = false;
+ else if (amdgpu_backlight == 1)
+ caps->aux_support = true;
++ else if (!IS_ERR_OR_NULL(panel_backlight_quirk) &&
++ panel_backlight_quirk->force_pwm)
++ caps->aux_support = false;
+ if (caps->aux_support)
+ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX;
+
+@@ -3751,8 +3756,6 @@ static void update_connector_ext_caps(st
+ else
+ caps->aux_min_input_signal = 1;
+
+- panel_backlight_quirk =
+- drm_get_panel_backlight_quirk(aconnector->drm_edid);
+ if (!IS_ERR_OR_NULL(panel_backlight_quirk)) {
+ if (panel_backlight_quirk->min_brightness) {
+ caps->min_input_signal =
+--- a/drivers/gpu/drm/drm_panel_backlight_quirks.c
++++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c
+@@ -21,6 +21,15 @@ struct drm_get_panel_backlight_quirk {
+ };
+
+ static const struct drm_get_panel_backlight_quirk drm_panel_min_backlight_quirks[] = {
++ /* Lenovo Legion 5 15ARH05, AUX backlight non-functional, force PWM */
++ {
++ .dmi_match.field = DMI_SYS_VENDOR,
++ .dmi_match.value = "LENOVO",
++ .dmi_match_other.field = DMI_PRODUCT_VERSION,
++ .dmi_match_other.value = "Lenovo Legion 5 15ARH05",
++ .ident.panel_id = drm_edid_encode_panel_id('B', 'O', 'E', 0x08df),
++ .quirk = { .force_pwm = true, },
++ },
+ /* 13 inch matte panel */
+ {
+ .dmi_match.field = DMI_BOARD_VENDOR,
+--- a/include/drm/drm_utils.h
++++ b/include/drm/drm_utils.h
+@@ -19,6 +19,7 @@ int drm_get_panel_orientation_quirk(int
+ struct drm_panel_backlight_quirk {
+ u16 min_brightness;
+ u32 brightness_mask;
++ bool force_pwm;
+ };
+
+ const struct drm_panel_backlight_quirk *
--- /dev/null
+From 9fa26b9eed6195bf840f39ac183b9a6237548755 Mon Sep 17 00:00:00 2001
+From: WenTao Liang <vulab@iscas.ac.cn>
+Date: Sun, 28 Jun 2026 15:27:40 +0800
+Subject: drm/amd/display: set new_stream to NULL after release
+
+From: WenTao Liang <vulab@iscas.ac.cn>
+
+commit 9fa26b9eed6195bf840f39ac183b9a6237548755 upstream.
+
+In dm_update_crtc_state(), the skip_modeset path releases new_stream
+via dc_stream_release() but does not set the pointer to NULL.
+
+If a later error (e.g., color management failure) triggers the fail
+label, the error path calls dc_stream_release() again on the same
+dangling pointer, causing a double release and potential use-after-free.
+
+Fix this by setting new_stream to NULL after the initial release.
+
+Fixes: 9b690ef3c704 ("drm/amd/display: Avoid full modeset when not required")
+Signed-off-by: WenTao Liang <vulab@iscas.ac.cn>
+Reviewed-by: George Zhang <george.zhang@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit 99f3af19073b3ddbfd96e789124cce12c4277b28)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+@@ -11383,6 +11383,7 @@ skip_modeset:
+ /* Release extra reference */
+ if (new_stream)
+ dc_stream_release(new_stream);
++ new_stream = NULL;
+
+ /*
+ * We want to do dc stream updates that do not require a
--- /dev/null
+From 85371c5ef502d10add72eab38711e191dccea981 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Timur=20Krist=C3=B3f?= <timur.kristof@gmail.com>
+Date: Mon, 13 Jul 2026 08:14:43 +0200
+Subject: drm/amd/pm/ci: Don't disable MCLK DPM on Bonaire 0x6658 (R7 260X)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Timur Kristóf <timur.kristof@gmail.com>
+
+commit 85371c5ef502d10add72eab38711e191dccea981 upstream.
+
+The old radeon driver has a documented workaround in ci_dpm.c
+which claims that Bonaire 0x6658 with old memory controller
+firmware is unstable with MCLK DPM, so as a precaution I
+disabled MCLK DPM on this ASIC in amdgpu.
+
+Note that the old MC firmware is not actually used with
+amdgpu, but in theory it's possible that the VBIOS sets
+up the ASIC with an old MC firmware that is already running
+when amdgpu initializes (in which case amdgpu doesn't
+load its own firmware).
+
+What I expected to happen is that the GPU would simply use
+its maximum memory clock, and indeed this is what seemed
+to happen according to amdgpu_pm_info which reads the
+current MCLK value from the SMU.
+However, some users reported a huge perf regression
+and upon a closer look it seems that the GPU seems to
+not actually use the highest MCLK value, despite the SMU
+reporting that it does.
+
+Let's not disable MCLK DPM on Bonaire 0x6658 (R7 260X).
+
+Keep MCLK DPM disabled on R9 M380 in the 2015 iMac
+because that still hangs if we enable it.
+
+Fixes: 9851f29cb06c ("drm/amd/pm/ci: Disable MCLK DPM on problematic CI ASICs")
+Signed-off-by: Timur Kristóf <timur.kristof@gmail.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit d34acad064ee7d82bd18f5d87592c422d4d323ac)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c
++++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c
+@@ -106,11 +106,8 @@ int hwmgr_early_init(struct pp_hwmgr *hw
+ hwmgr->od_enabled = false;
+ switch (hwmgr->chip_id) {
+ case CHIP_BONAIRE:
+- /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM
+- * R7 260X cards with old MC ucode: MCLK DPM is unstable
+- */
+- if (adev->pdev->subsystem_vendor == 0x106B ||
+- adev->pdev->device == 0x6658) {
++ /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM */
++ if (adev->pdev->subsystem_vendor == 0x106B) {
+ dev_info(adev->dev, "disabling MCLK DPM on quirky ASIC");
+ adev->pm.pp_feature &= ~PP_MCLK_DPM_MASK;
+ hwmgr->feature_mask &= ~PP_MCLK_DPM_MASK;
--- /dev/null
+From db7e8108809a2245f0a17ba323f027cac0941ffb Mon Sep 17 00:00:00 2001
+From: Mario Limonciello <mario.limonciello@amd.com>
+Date: Wed, 8 Jul 2026 14:35:14 -0500
+Subject: drm/amdgpu: Fix VFCT bus number matching with soft filter
+
+From: Mario Limonciello <mario.limonciello@amd.com>
+
+commit db7e8108809a2245f0a17ba323f027cac0941ffb upstream.
+
+On systems where PCI bus renumbering occurs (e.g. pci=realloc,
+resource conflicts), the runtime bus number may differ from the
+BIOS POST bus number recorded in the VFCT table. This causes
+amdgpu_acpi_vfct_bios() to fail finding the VBIOS even though
+the correct device entry exists.
+
+Introduce amdgpu_acpi_vfct_match() which treats the bus number
+as a soft filter: vendor/device/function identity is the hard
+requirement, while exact bus match is the preferred path. When
+bus numbers disagree but device identity matches, accept the
+VFCT entry and log a dev_notice for diagnostics.
+
+Reported-by: Oz Tiram <oz@shift-computing.de>
+Closes: https://lore.kernel.org/amd-gfx/20260621173211.28443-1-oz@shift-computing.de/
+Reviewed-by: Alex Deucher <alexander.deucher@amd.com>
+Link: https://patch.msgid.link/20260708193518.702584-2-mario.limonciello@amd.com
+Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit 11c141672045ffc0187aa604f2c0f597bc334fb2)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 45 +++++++++++++++++++++++++++----
+ 1 file changed, 40 insertions(+), 5 deletions(-)
+
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c
+@@ -355,6 +355,45 @@ static bool amdgpu_read_disabled_bios(st
+ }
+
+ #ifdef CONFIG_ACPI
++/**
++ * amdgpu_acpi_vfct_match() - Check if a VFCT entry matches the device
++ * @adev: AMDGPU device
++ * @vhdr: VFCT image header to check
++ *
++ * VFCT entries contain the PCI bus number as recorded during BIOS POST.
++ * On systems where the kernel renumbers PCI buses (e.g. pci=realloc or
++ * resource conflicts), the runtime bus number may differ from the POST
++ * value. Match by device identity (vendor + device + function) and use
++ * the bus number as a preference: exact bus match is preferred, but when
++ * the bus numbers disagree we accept the entry if the device identity
++ * matches.
++ *
++ * Returns: 0 on match, -ENODEV on no match
++ */
++static int amdgpu_acpi_vfct_match(struct amdgpu_device *adev,
++ VFCT_IMAGE_HEADER *vhdr)
++{
++ /* Vendor and device IDs must always match */
++ if (vhdr->VendorID != adev->pdev->vendor ||
++ vhdr->DeviceID != adev->pdev->device)
++ return -ENODEV;
++
++ if (vhdr->PCIDevice != PCI_SLOT(adev->pdev->devfn) ||
++ vhdr->PCIFunction != PCI_FUNC(adev->pdev->devfn))
++ return -ENODEV;
++
++ /* Exact bus number match - preferred */
++ if (vhdr->PCIBus == adev->pdev->bus->number)
++ return 0;
++
++ /* Bus mismatch but device identity matches (PCI renumbering case) */
++ dev_notice(adev->dev,
++ "VFCT bus number mismatch: table %u != runtime %u, matching by device identity (vendor 0x%04x device 0x%04x)\n",
++ vhdr->PCIBus, adev->pdev->bus->number,
++ adev->pdev->vendor, adev->pdev->device);
++ return 0;
++}
++
+ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev)
+ {
+ struct acpi_table_header *hdr;
+@@ -391,11 +430,7 @@ static bool amdgpu_acpi_vfct_bios(struct
+ }
+
+ if (vhdr->ImageLength &&
+- vhdr->PCIBus == adev->pdev->bus->number &&
+- vhdr->PCIDevice == PCI_SLOT(adev->pdev->devfn) &&
+- vhdr->PCIFunction == PCI_FUNC(adev->pdev->devfn) &&
+- vhdr->VendorID == adev->pdev->vendor &&
+- vhdr->DeviceID == adev->pdev->device) {
++ !amdgpu_acpi_vfct_match(adev, vhdr)) {
+ adev->bios = kmemdup(&vbios->VbiosContent,
+ vhdr->ImageLength,
+ GFP_KERNEL);
--- /dev/null
+From 65bff26617607c1331283232016c0e89088c5b78 Mon Sep 17 00:00:00 2001
+From: Mario Limonciello <mario.limonciello@amd.com>
+Date: Wed, 8 Jul 2026 14:35:15 -0500
+Subject: drm/amdgpu: Release VFCT ACPI table reference
+
+From: Mario Limonciello <mario.limonciello@amd.com>
+
+commit 65bff26617607c1331283232016c0e89088c5b78 upstream.
+
+amdgpu_acpi_vfct_bios() fetches the VFCT table with acpi_get_table()
+but never releases it. acpi_get_table() takes a reference on the
+table (incrementing its validation_count and mapping it on the 0->1
+transition); without a paired acpi_put_table() the mapping is leaked
+on every call, whether or not a matching VBIOS image is found.
+
+Route all exit paths after the table is acquired through a common
+acpi_put_table(). The VBIOS image is copied out with kmemdup() before
+the table is released, so it remains valid for the caller.
+
+Reviewed-by: Alex Deucher <alexander.deucher@amd.com>
+Link: https://patch.msgid.link/20260708193518.702584-3-mario.limonciello@amd.com
+Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit ca5988682b4cba4cd125a0fa99b2de1239164ae4)
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 17 +++++++++++------
+ 1 file changed, 11 insertions(+), 6 deletions(-)
+
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c
+@@ -361,13 +361,14 @@ static bool amdgpu_acpi_vfct_bios(struct
+ acpi_size tbl_size;
+ UEFI_ACPI_VFCT *vfct;
+ unsigned int offset;
++ bool r = false;
+
+ if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr)))
+ return false;
+ tbl_size = hdr->length;
+ if (tbl_size < sizeof(UEFI_ACPI_VFCT)) {
+ dev_info(adev->dev, "ACPI VFCT table present but broken (too short #1),skipping\n");
+- return false;
++ goto out;
+ }
+
+ vfct = (UEFI_ACPI_VFCT *)hdr;
+@@ -380,13 +381,13 @@ static bool amdgpu_acpi_vfct_bios(struct
+ offset += sizeof(VFCT_IMAGE_HEADER);
+ if (offset > tbl_size) {
+ dev_info(adev->dev, "ACPI VFCT image header truncated,skipping\n");
+- return false;
++ goto out;
+ }
+
+ offset += vhdr->ImageLength;
+ if (offset > tbl_size) {
+ dev_info(adev->dev, "ACPI VFCT image truncated,skipping\n");
+- return false;
++ goto out;
+ }
+
+ if (vhdr->ImageLength &&
+@@ -401,15 +402,19 @@ static bool amdgpu_acpi_vfct_bios(struct
+
+ if (!check_atom_bios(adev, vhdr->ImageLength)) {
+ amdgpu_bios_release(adev);
+- return false;
++ goto out;
+ }
+ adev->bios_size = vhdr->ImageLength;
+- return true;
++ r = true;
++ goto out;
+ }
+ }
+
+ dev_info(adev->dev, "ACPI VFCT table present but broken (too short #2),skipping\n");
+- return false;
++
++out:
++ acpi_put_table(hdr);
++ return r;
+ }
+ #else
+ static inline bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev)
--- /dev/null
+From 4a2c8cbe9bcba170706fdf08b1c84b6cbcf5b044 Mon Sep 17 00:00:00 2001
+From: Osama Abdelkader <osama.abdelkader@gmail.com>
+Date: Tue, 14 Jul 2026 18:30:55 +0200
+Subject: drm/panthor: return error on truncated firmware
+
+From: Osama Abdelkader <osama.abdelkader@gmail.com>
+
+commit 4a2c8cbe9bcba170706fdf08b1c84b6cbcf5b044 upstream.
+
+panthor_fw_load() detects truncated firmware images, but jumps to the
+common cleanup path without setting ret. If no previous error was recorded,
+the function can return 0 and treat the invalid firmware as successfully
+loaded.
+
+Set ret to -EINVAL before leaving the truncated-image path.
+
+Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block")
+Cc: stable@vger.kernel.org
+Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
+Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>
+Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
+Link: https://patch.msgid.link/20260714163056.22329-1-osama.abdelkader@gmail.com
+Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/panthor/panthor_fw.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/gpu/drm/panthor/panthor_fw.c
++++ b/drivers/gpu/drm/panthor/panthor_fw.c
+@@ -777,6 +777,7 @@ static int panthor_fw_load(struct pantho
+ }
+
+ if (hdr.size > iter.size) {
++ ret = -EINVAL;
+ drm_err(&ptdev->base, "Firmware image is truncated\n");
+ goto out;
+ }
--- /dev/null
+From 5b7b3b6595ee77d01c7463757baed114786094dd Mon Sep 17 00:00:00 2001
+From: Matthew Brost <matthew.brost@intel.com>
+Date: Thu, 2 Jul 2026 14:48:15 -0700
+Subject: drm/ttm: Account for NULL and handle pages in ttm_pool_backup
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Matthew Brost <matthew.brost@intel.com>
+
+commit 5b7b3b6595ee77d01c7463757baed114786094dd upstream.
+
+Pages in ttm_pool_backup can be NULL or backup handles
+(ttm_backup_page_ptr_is_handle()), neither of which can be passed to
+set_pages_array_wb() or freed. Add a dedicated WB pass before the
+dma/purge loop that walks allocations using the same i += num_pages
+stride, skipping NULL and handle entries, and calls set_pages_array_wb()
+once per contiguous run of real pages. Apply the same NULL/handle guard
+to the dma/purge loop.
+
+Fixes the following oops:
+
+Oops: general protection fault, kernel NULL pointer dereference 0x0: 0000 [#1] SMP NOPTI
+RIP: 0010:__cpa_process_fault+0xf8/0x770
+RSP: 0018:ffffc90000a87718 EFLAGS: 00010287
+RAX: 0000000000000000 RBX: ffffc90000a87868 RCX: 0000000000000000
+RDX: 0000000000001000 RSI: 0005088000000000 RDI: ffffffff827c5f34
+RBP: 0005088000000000 R08: ffffc90000a877cb R09: ffffc90000a877d0
+R10: 0000000000000000 R11: 000000000000001b R12: 000ffffffffff000
+R13: ffffc90000a87868 R14: ffffc90000a87868 R15: ffff88815b882ae0
+FS: 0000000000000000(0000) GS:ffff8884ec840000(0000) knlGS:0000000000000000
+CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
+CR2: 00007f930b844000 CR3: 000000000262e003 CR4: 0000000008f70ef0
+PKRU: 55555554
+Call Trace:
+ <TASK>
+ __change_page_attr_set_clr+0x989/0xe90
+ ? __purge_vmap_area_lazy+0x6c/0x3a0
+ ? _vm_unmap_aliases+0x250/0x2a0
+ set_pages_array_wb+0x7f/0x120
+ ttm_pool_backup+0x4c9/0x5b0 [ttm]
+ ? dma_resv_wait_timeout+0x3b/0xf0
+ ttm_tt_backup+0x32/0x60 [ttm]
+ ttm_bo_shrink+0x66/0x110 [ttm]
+ xe_bo_shrink_purge+0x12b/0x1b0 [xe]
+ xe_bo_shrink+0xbb/0x270 [xe]
+ __xe_shrinker_walk+0xf7/0x160 [xe]
+ xe_shrinker_walk+0x9d/0xc0 [xe]
+ xe_shrinker_scan+0x11f/0x210 [xe]
+ do_shrink_slab+0x13b/0x270
+ shrink_slab+0xf1/0x400
+ shrink_node+0x352/0x8a0
+ balance_pgdat+0x32c/0x700
+ kswapd+0x205/0x2f0
+ ? __pfx_autoremove_wake_function+0x10/0x10
+ ? __pfx_kswapd+0x10/0x10
+ kthread+0xd1/0x110
+ ? __pfx_kthread+0x10/0x10
+ ret_from_fork+0x1b1/0x200
+ ? __pfx_kthread+0x10/0x10
+ ret_from_fork_asm+0x1a/0x30
+ </TASK>
+
+Cc: Christian Koenig <christian.koenig@amd.com>
+Cc: Huang Rui <ray.huang@amd.com>
+Cc: Matthew Auld <matthew.auld@intel.com>
+Cc: Matthew Brost <matthew.brost@intel.com>
+Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
+Cc: Maxime Ripard <mripard@kernel.org>
+Cc: Thomas Zimmermann <tzimmermann@suse.de>
+Cc: David Airlie <airlied@gmail.com>
+Cc: Simona Vetter <simona@ffwll.ch>
+Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
+Cc: dri-devel@lists.freedesktop.org
+Cc: linux-kernel@vger.kernel.org
+Cc: stable@vger.kernel.org
+Fixes: b63d715b8090 ("drm/ttm/pool, drm/ttm/tt: Provide a helper to shrink pages")
+Cc: stable@vger.kernel.org
+Assisted-by: GitHub_Copilot:claude-opus-4.8
+Signed-off-by: Matthew Brost <matthew.brost@intel.com>
+Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
+Link: https://patch.msgid.link/20260702214815.4009271-1-matthew.brost@intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/ttm/ttm_pool.c | 34 ++++++++++++++++++++++++++++++----
+ 1 file changed, 30 insertions(+), 4 deletions(-)
+
+--- a/drivers/gpu/drm/ttm/ttm_pool.c
++++ b/drivers/gpu/drm/ttm/ttm_pool.c
+@@ -981,9 +981,31 @@ long ttm_pool_backup(struct ttm_pool *po
+ return -EBUSY;
+
+ #ifdef CONFIG_X86
+- /* Anything returned to the system needs to be cached. */
+- if (tt->caching != ttm_cached)
+- set_pages_array_wb(tt->pages, tt->num_pages);
++ /* Anything returned to the system needs to be cached. Walk allocations
++ * skipping NULL pages and issue set_pages_array_wb() per contiguous run.
++ */
++ if (tt->caching != ttm_cached) {
++ pgoff_t run_start = 0, run_count = 0;
++
++ for (i = 0; i < tt->num_pages; i += num_pages) {
++ page = tt->pages[i];
++ if (unlikely(!page || ttm_backup_page_ptr_is_handle(page))) {
++ if (run_count) {
++ set_pages_array_wb(&tt->pages[run_start],
++ run_count);
++ run_count = 0;
++ }
++ num_pages = 1;
++ continue;
++ }
++ num_pages = 1UL << ttm_pool_page_order(pool, page);
++ if (!run_count)
++ run_start = i;
++ run_count += num_pages;
++ }
++ if (run_count)
++ set_pages_array_wb(&tt->pages[run_start], run_count);
++ }
+ #endif
+
+ if (tt->dma_address || flags->purge) {
+@@ -991,7 +1013,7 @@ long ttm_pool_backup(struct ttm_pool *po
+ unsigned int order;
+
+ page = tt->pages[i];
+- if (unlikely(!page)) {
++ if (unlikely(!page || ttm_backup_page_ptr_is_handle(page))) {
+ num_pages = 1;
+ continue;
+ }
+@@ -1034,6 +1056,10 @@ long ttm_pool_backup(struct ttm_pool *po
+ if (unlikely(!page))
+ continue;
+
++ /* Already-handled entry from a previous attempt. */
++ if (unlikely(ttm_backup_page_ptr_is_handle(page)))
++ continue;
++
+ ttm_pool_split_for_swap(pool, page);
+
+ shandle = ttm_backup_backup_page(backup, page, flags->writeback, i,
drm-amd-pm-fix-smu14-power-limit-range-calculation.patch
drm-gfx10-program-db_ring_control.patch
drm-virtio-don-t-detach-gem-from-a-non-created-context.patch
+drm-ttm-account-for-null-and-handle-pages-in-ttm_pool_backup.patch
+drm-panthor-return-error-on-truncated-firmware.patch
+drm-amdgpu-release-vfct-acpi-table-reference.patch
+drm-amdgpu-fix-vfct-bus-number-matching-with-soft-filter.patch
+drm-amd-pm-ci-don-t-disable-mclk-dpm-on-bonaire-0x6658-r7-260x.patch
+drm-amd-display-set-new_stream-to-null-after-release.patch
+drm-amd-display-consolidate-dcn-vblank-flip-handling-onto-vupdate_no_lock.patch
+drm-amd-display-dce100-skip-non-dp-stream-encoders-for-dp-mst.patch
+drm-amd-display-force-pwm-backlight-on-lenovo-legion-5-15arh05.patch
+drm-amd-display-fix-backlight-max_brightness-to-match-exported-range.patch