[PATCH 15/19] drm/i915/display: PSR Add delayed work to exit DC3CO

Dibin Moolakadan Subrahmanian
2026-03-26T17:15:47Z

For DC3CO, idle_frames is programmed to 0, so PSR does not
enter deep sleep. Add delayed work to schedule DC3CO exit
after an idle duration derived from frame time (minimum
equivalent of 6 frames).

The work is re-armed from the PSR flush path on relevant
frontbuffer activity, and once the display remains idle,
DC3CO is disabled and DC6 is enabled to allow deeper power
savings.

Signed-off-by: Dibin Moolakadan Subrahmanian <dibin.moolakadan.subrahmanian@intel.com>
---
.../drm/i915/display/intel_display_types.h | 2 +
drivers/gpu/drm/i915/display/intel_psr.c | 48 +++++++++++++++++++
2 files changed, 50 insertions(+)

diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index d0d2cda3d669..0c8958338f76 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -1785,6 +1785,8 @@ struct intel_psr {
bool irq_aux_error;
/* DC3CO eligibility used to control PSR configuration */
bool dc3co_eligible;
+ /* DC3CO disable work*/
+ struct delayed_work dc3co_work;
u16 su_w_granularity;
u16 su_y_granularity;
bool source_panel_replay_support;
diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
index 16a9f4111ac8..f3476118b8d0 100644
--- a/drivers/gpu/drm/i915/display/intel_psr.c
+++ b/drivers/gpu/drm/i915/display/intel_psr.c
@@ -1701,6 +1701,50 @@ static bool intel_psr_needs_wa_18037818876(struct intel_dp *intel_dp,
!crtc_state->has_sel_update);
}

+static void psr2_dc3co_disable_locked(struct intel_dp *intel_dp)
+{
+ struct intel_display *display = to_intel_display(intel_dp);
+
+ if (intel_dp->psr.dc3co_eligible) {
+ intel_dp->psr.dc3co_eligible = false;
+ intel_display_power_set_target_dc_state(display, DC_STATE_EN_UPTO_DC6);
+ }
+}
+
+static void psr2_dc3co_disable_work(struct work_struct *work)
+{
+ struct intel_dp *intel_dp =
+ container_of(work, typeof(*intel_dp), psr.dc3co_work.work);
+
+ mutex_lock(&intel_dp->psr.lock);
+ psr2_dc3co_disable_locked(intel_dp);
+ mutex_unlock(&intel_dp->psr.lock);
+}
+
+static void
+psr2_dc3co_flush_locked(struct intel_dp *intel_dp, unsigned int frontbuffer_bits,
+ enum fb_op_origin origin)
+{
+ struct intel_display *display = to_intel_display(intel_dp);
+
+ if (!intel_dp->psr.dc3co_eligible)
+ return;
+
+ if (!intel_dp->psr.sel_update_enabled ||
+ !intel_dp->psr.active)
+ return;
+ /*
+ * At every frontbuffer flush flip event modified delay of delayed work,
+ * when delayed work schedules that means display has been idle.
+ */
+ if (!(frontbuffer_bits &
+ INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe)))
+ return;
+
+ mod_delayed_work(display->wq.unordered, &intel_dp->psr.dc3co_work,
+ intel_dp->psr.dc3co_exit_delay);
+}
+
static
void intel_psr_set_non_psr_pipes(struct intel_dp *intel_dp,
struct intel_crtc_state *crtc_state)
@@ -2273,6 +2317,7 @@ void intel_psr_disable(struct intel_dp *intel_dp,

mutex_unlock(&intel_dp->psr.lock);
cancel_work_sync(&intel_dp->psr.work);
+ cancel_delayed_work_sync(&intel_dp->psr.dc3co_work);
}

/**
@@ -2303,6 +2348,7 @@ void intel_psr_pause(struct intel_dp *intel_dp)
mutex_unlock(&psr->lock);

cancel_work_sync(&psr->work);
+ cancel_delayed_work_sync(&psr->dc3co_work);
}

/**
@@ -3527,6 +3573,7 @@ void intel_psr_flush(struct intel_display *display,
if (origin == ORIGIN_FLIP ||
(origin == ORIGIN_CURSOR_UPDATE &&
!intel_dp->psr.psr2_sel_fetch_enabled)) {
+ psr2_dc3co_flush_locked(intel_dp, frontbuffer_bits, origin);
goto unlock;
}

@@ -3585,6 +3632,7 @@ void intel_psr_init(struct intel_dp *intel_dp)
intel_dp->psr.link_standby = connector->panel.vbt.psr.full_link;

INIT_WORK(&intel_dp->psr.work, intel_psr_work);
+ INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, psr2_dc3co_disable_work);
mutex_init(&intel_dp->psr.lock);
}

--
2.43.0