[PATCH v2 11/13] drm/i915/display: PSR Add delayed work to exit DC3CO

Dibin Moolakadan Subrahmanian
2026-04-22T16:26:27Z

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. Once the display remains idle, DC3CO is disabled, idle frames
are reprogrammed to their normal value, and DC6 is enabled to allow
deeper power savings.

Changes in v2:
- Squash "PSR set idle frames while exit from DC3CO"
into this patch (Uma Shankar)
- Add cancel_delayed_work() in intel_psr_disable_locked()
before clearing dc3co_eligible (Uma Shankar)

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 | 50 +++++++++++++++++++
2 files changed, 52 insertions(+)

diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index 28ab686b702a..fb5c12bb5b5c 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -1771,6 +1771,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 ff9ce7d2a5aa..36180206d3ad 100644
--- a/drivers/gpu/drm/i915/display/intel_psr.c
+++ b/drivers/gpu/drm/i915/display/intel_psr.c
@@ -1728,6 +1728,51 @@ 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);
+ psr2_program_idle_frames(intel_dp, psr_compute_idle_frames(intel_dp));
+ }
+}
+
+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)
@@ -2264,6 +2309,7 @@ static void intel_psr_disable_locked(struct intel_dp *intel_dp)
intel_dp->psr.psr2_sel_fetch_cff_enabled = false;
intel_dp->psr.active_non_psr_pipes = 0;
intel_dp->psr.pkg_c_latency_used = 0;
+ cancel_delayed_work(&intel_dp->psr.dc3co_work);
intel_dp->psr.dc3co_eligible = false;
}

@@ -2294,6 +2340,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);
}

/**
@@ -2324,6 +2371,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);
}

/**
@@ -3558,6 +3606,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;
}

@@ -3616,6 +3665,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