From bd17fc4b6b2b522567982b7a0b2f6c5972e504fa Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:11 +0200 Subject: [PATCH 01/16] drm/i915: Extract intel_cdclk_update_hw_state() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Hoist the cdclk stuff into a separate function from intel_modeset_readout_hw_state() so that the details are better hidden inside intel_cdclk.c. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-10-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_cdclk.c | 29 ++++++++++++++++++- drivers/gpu/drm/i915/display/intel_cdclk.h | 2 +- .../drm/i915/display/intel_modeset_setup.c | 16 ++-------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index 4b7058e65588..947833a96ab7 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -2788,7 +2788,7 @@ static int intel_planes_min_cdclk(const struct intel_crtc_state *crtc_state) return min_cdclk; } -int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state) +static int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state) { int min_cdclk; @@ -3340,6 +3340,33 @@ int intel_modeset_calc_cdclk(struct intel_atomic_state *state) return 0; } +void intel_cdclk_update_hw_state(struct intel_display *display) +{ + struct intel_cdclk_state *cdclk_state = + to_intel_cdclk_state(display->cdclk.obj.state); + struct intel_crtc *crtc; + + cdclk_state->active_pipes = 0; + + for_each_intel_crtc(display->drm, crtc) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + enum pipe pipe = crtc->pipe; + int min_cdclk = 0; + + if (crtc_state->hw.active) { + cdclk_state->active_pipes |= BIT(pipe); + + min_cdclk = intel_crtc_compute_min_cdclk(crtc_state); + if (drm_WARN_ON(display->drm, min_cdclk < 0)) + min_cdclk = 0; + } + + cdclk_state->min_cdclk[pipe] = min_cdclk; + cdclk_state->min_voltage_level[pipe] = crtc_state->min_voltage_level; + } +} + void intel_cdclk_crtc_disable_noatomic(struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(crtc); diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.h b/drivers/gpu/drm/i915/display/intel_cdclk.h index 689e12e2196b..a1cefd455d92 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.h +++ b/drivers/gpu/drm/i915/display/intel_cdclk.h @@ -59,7 +59,6 @@ struct intel_cdclk_state { bool disable_pipes; }; -int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state); void intel_cdclk_init_hw(struct intel_display *display); void intel_cdclk_uninit_hw(struct intel_display *display); void intel_init_cdclk_hooks(struct intel_display *display); @@ -84,6 +83,7 @@ int intel_cdclk_atomic_check(struct intel_atomic_state *state, int intel_cdclk_state_set_joined_mbus(struct intel_atomic_state *state, bool joined_mbus); struct intel_cdclk_state * intel_atomic_get_cdclk_state(struct intel_atomic_state *state); +void intel_cdclk_update_hw_state(struct intel_display *display); void intel_cdclk_crtc_disable_noatomic(struct intel_crtc *crtc); #define to_intel_cdclk_state(global_state) \ diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index e9b0533526f6..1cfa03bd3224 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -693,8 +693,6 @@ static void readout_plane_state(struct drm_i915_private *i915) static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) { struct intel_display *display = &i915->display; - struct intel_cdclk_state *cdclk_state = - to_intel_cdclk_state(i915->display.cdclk.obj.state); struct intel_dbuf_state *dbuf_state = to_intel_dbuf_state(i915->display.dbuf.obj.state); struct intel_pmdemand_state *pmdemand_state = @@ -730,7 +728,6 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) str_enabled_disabled(crtc_state->hw.active)); } - cdclk_state->active_pipes = active_pipes; dbuf_state->active_pipes = active_pipes; readout_plane_state(i915); @@ -833,7 +830,6 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) struct intel_crtc_state *crtc_state = to_intel_crtc_state(crtc->base.state); struct intel_plane *plane; - int min_cdclk = 0; if (crtc_state->hw.active) { /* @@ -882,22 +878,14 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) crtc_state->min_cdclk[plane->id]); } - if (crtc_state->hw.active) { - min_cdclk = intel_crtc_compute_min_cdclk(crtc_state); - if (drm_WARN_ON(&i915->drm, min_cdclk < 0)) - min_cdclk = 0; - } - - cdclk_state->min_cdclk[crtc->pipe] = min_cdclk; - cdclk_state->min_voltage_level[crtc->pipe] = - crtc_state->min_voltage_level; - intel_pmdemand_update_port_clock(display, pmdemand_state, pipe, crtc_state->port_clock); intel_bw_crtc_update(bw_state, crtc_state); } + intel_cdclk_update_hw_state(display); + intel_pmdemand_init_pmdemand_params(display, pmdemand_state); } -- 2.51.0 From ad2d834f888b0eeed8675e82705258fdd81021b2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:12 +0200 Subject: [PATCH 02/16] drm/i915: Extract intel_bw_update_hw_state() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Hoist the bw stuff into a separate function from intel_modeset_readout_hw_state() so that the details are better hidden inside intel_bw.c. We can also skip the whole thing on pre-skl since the dbuf state isn't actually used on those platforms. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-11-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 21 +++++++++++++++++-- drivers/gpu/drm/i915/display/intel_bw.h | 4 ++-- .../drm/i915/display/intel_modeset_setup.c | 5 +---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 417d2d527ae4..0090ee7e73a1 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -806,8 +806,8 @@ static int intel_bw_crtc_min_cdclk(const struct intel_crtc_state *crtc_state) return DIV_ROUND_UP_ULL(mul_u32_u32(intel_bw_crtc_data_rate(crtc_state), 10), 512); } -void intel_bw_crtc_update(struct intel_bw_state *bw_state, - const struct intel_crtc_state *crtc_state) +static void intel_bw_crtc_update(struct intel_bw_state *bw_state, + const struct intel_crtc_state *crtc_state) { struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); struct drm_i915_private *i915 = to_i915(crtc->base.dev); @@ -1422,6 +1422,23 @@ int intel_bw_atomic_check(struct intel_atomic_state *state) return 0; } +void intel_bw_update_hw_state(struct intel_display *display) +{ + struct intel_bw_state *bw_state = + to_intel_bw_state(display->bw.obj.state); + struct intel_crtc *crtc; + + if (DISPLAY_VER(display) < 9) + return; + + for_each_intel_crtc(display->drm, crtc) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + intel_bw_crtc_update(bw_state, crtc_state); + } +} + void intel_bw_crtc_disable_noatomic(struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(crtc); diff --git a/drivers/gpu/drm/i915/display/intel_bw.h b/drivers/gpu/drm/i915/display/intel_bw.h index bd94dde207ee..3313e4eac4f0 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.h +++ b/drivers/gpu/drm/i915/display/intel_bw.h @@ -16,6 +16,7 @@ struct drm_i915_private; struct intel_atomic_state; struct intel_crtc; struct intel_crtc_state; +struct intel_display; struct intel_dbuf_bw { unsigned int max_bw[I915_MAX_DBUF_SLICES]; @@ -74,14 +75,13 @@ intel_atomic_get_bw_state(struct intel_atomic_state *state); void intel_bw_init_hw(struct drm_i915_private *dev_priv); int intel_bw_init(struct drm_i915_private *dev_priv); int intel_bw_atomic_check(struct intel_atomic_state *state); -void intel_bw_crtc_update(struct intel_bw_state *bw_state, - const struct intel_crtc_state *crtc_state); int icl_pcode_restrict_qgv_points(struct drm_i915_private *dev_priv, u32 points_mask); int intel_bw_calc_min_cdclk(struct intel_atomic_state *state, bool *need_cdclk_calc); int intel_bw_min_cdclk(struct drm_i915_private *i915, const struct intel_bw_state *bw_state); +void intel_bw_update_hw_state(struct intel_display *display); void intel_bw_crtc_disable_noatomic(struct intel_crtc *crtc); #endif /* __INTEL_BW_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index 1cfa03bd3224..70fad9f89cea 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -825,8 +825,6 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) drm_connector_list_iter_end(&conn_iter); for_each_intel_crtc(&i915->drm, crtc) { - struct intel_bw_state *bw_state = - to_intel_bw_state(i915->display.bw.obj.state); struct intel_crtc_state *crtc_state = to_intel_crtc_state(crtc->base.state); struct intel_plane *plane; @@ -880,10 +878,9 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) intel_pmdemand_update_port_clock(display, pmdemand_state, pipe, crtc_state->port_clock); - - intel_bw_crtc_update(bw_state, crtc_state); } + intel_bw_update_hw_state(display); intel_cdclk_update_hw_state(display); intel_pmdemand_init_pmdemand_params(display, pmdemand_state); -- 2.51.0 From 7b65b24a4ecbe11763a68625e7f626d3b913f041 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:13 +0200 Subject: [PATCH 03/16] drm/i915: Update bw_state->active_pipes during readout MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Update bw_state->active_pipes during readout. This was completely missing from the current readout code. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-12-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 0090ee7e73a1..30e6f33d6f30 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -1431,9 +1431,15 @@ void intel_bw_update_hw_state(struct intel_display *display) if (DISPLAY_VER(display) < 9) return; + bw_state->active_pipes = 0; + for_each_intel_crtc(display->drm, crtc) { const struct intel_crtc_state *crtc_state = to_intel_crtc_state(crtc->base.state); + enum pipe pipe = crtc->pipe; + + if (crtc_state->hw.active) + bw_state->active_pipes |= BIT(pipe); intel_bw_crtc_update(bw_state, crtc_state); } -- 2.51.0 From ff79a131d37a9ab6301c310f8ea9c12e93212baa Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:14 +0200 Subject: [PATCH 04/16] drm/i915: Skip some bw_state readout on pre-icl MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit We only compute bw_state->data_rate and bw_state->num_active_planes on icl+. Do the same during readout so that we don't leave random junk inside the state. v2: Skip the whole intel_bw_crtc_update() (Vinod) Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-13-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 30e6f33d6f30..e4d79b496e12 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -1441,7 +1441,8 @@ void intel_bw_update_hw_state(struct intel_display *display) if (crtc_state->hw.active) bw_state->active_pipes |= BIT(pipe); - intel_bw_crtc_update(bw_state, crtc_state); + if (DISPLAY_VER(display) >= 11) + intel_bw_crtc_update(bw_state, crtc_state); } } -- 2.51.0 From e45b8192c7e757d33dd1a7f1b1ea6696fbdc218b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:15 +0200 Subject: [PATCH 05/16] sem/i915: Simplify intel_cdclk_update_hw_state() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit intel_crtc_calculate_min_cdclk() can't return an error (since commit 5ac860cc5254 ("drm/i915: Fix DBUF bandwidth vs. cdclk handling")) so there is no point in checking for one. Also we can just call it unconditionally since it itself checks crtc_state->hw.enabled. We are currently checking crtc_state->hw.active in the readout path, but active==enabled during readout, and arguably enabled is the more correct thing to check anyway. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-14-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_cdclk.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index 947833a96ab7..62caee4a8b64 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -3352,17 +3352,11 @@ void intel_cdclk_update_hw_state(struct intel_display *display) const struct intel_crtc_state *crtc_state = to_intel_crtc_state(crtc->base.state); enum pipe pipe = crtc->pipe; - int min_cdclk = 0; - if (crtc_state->hw.active) { + if (crtc_state->hw.active) cdclk_state->active_pipes |= BIT(pipe); - min_cdclk = intel_crtc_compute_min_cdclk(crtc_state); - if (drm_WARN_ON(display->drm, min_cdclk < 0)) - min_cdclk = 0; - } - - cdclk_state->min_cdclk[pipe] = min_cdclk; + cdclk_state->min_cdclk[pipe] = intel_crtc_compute_min_cdclk(crtc_state); cdclk_state->min_voltage_level[pipe] = crtc_state->min_voltage_level; } } -- 2.51.0 From 003ec4ce424cc3421cf82ec1b6f004f9c0fcb8a3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:16 +0200 Subject: [PATCH 06/16] drm/i915: Simplify cdclk_disable_noatomic() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Instead of hand rolling the cdclk state disabling for a pipe in noatomic() let's just recompute the whole thing from scratch. Less code we have to remember to keep in sync. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-15-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_cdclk.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index 62caee4a8b64..2a8749a0213e 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -3364,13 +3364,8 @@ void intel_cdclk_update_hw_state(struct intel_display *display) void intel_cdclk_crtc_disable_noatomic(struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(crtc); - struct intel_cdclk_state *cdclk_state = - to_intel_cdclk_state(display->cdclk.obj.state); - enum pipe pipe = crtc->pipe; - cdclk_state->min_cdclk[pipe] = 0; - cdclk_state->min_voltage_level[pipe] = 0; - cdclk_state->active_pipes &= ~BIT(pipe); + intel_cdclk_update_hw_state(display); } static int intel_compute_max_dotclk(struct intel_display *display) -- 2.51.0 From dbf1b1ed55badf205f649c626338288f1d076391 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:17 +0200 Subject: [PATCH 07/16] drm/i915: Split wm sanitize from readout MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit I'll need to move the wm readout to an earlier point in the sequence (since the bw state readout will need ddb information from the wm readout). But (at least for now) the wm sanitation will need to stay put as it needs to also sanitize things for any pipes/planes we disable later during the hw state takeover. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-16-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/i9xx_wm.c | 18 ++++-------------- .../gpu/drm/i915/display/intel_display_core.h | 1 + .../gpu/drm/i915/display/intel_modeset_setup.c | 1 + drivers/gpu/drm/i915/display/intel_wm.c | 6 ++++++ drivers/gpu/drm/i915/display/intel_wm.h | 1 + drivers/gpu/drm/i915/display/skl_watermark.c | 7 +++---- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/i915/display/i9xx_wm.c b/drivers/gpu/drm/i915/display/i9xx_wm.c index 497850a6ac81..7c80e37c1c5f 100644 --- a/drivers/gpu/drm/i915/display/i9xx_wm.c +++ b/drivers/gpu/drm/i915/display/i9xx_wm.c @@ -3902,12 +3902,6 @@ static void g4x_wm_sanitize(struct drm_i915_private *dev_priv) mutex_unlock(&dev_priv->display.wm.wm_mutex); } -static void g4x_wm_get_hw_state_and_sanitize(struct drm_i915_private *i915) -{ - g4x_wm_get_hw_state(i915); - g4x_wm_sanitize(i915); -} - static void vlv_wm_get_hw_state(struct drm_i915_private *dev_priv) { struct vlv_wm_values *wm = &dev_priv->display.wm.vlv; @@ -4055,12 +4049,6 @@ static void vlv_wm_sanitize(struct drm_i915_private *dev_priv) mutex_unlock(&dev_priv->display.wm.wm_mutex); } -static void vlv_wm_get_hw_state_and_sanitize(struct drm_i915_private *i915) -{ - vlv_wm_get_hw_state(i915); - vlv_wm_sanitize(i915); -} - /* * FIXME should probably kill this and improve * the real watermark readout/sanitation instead @@ -4122,14 +4110,16 @@ static const struct intel_wm_funcs vlv_wm_funcs = { .initial_watermarks = vlv_initial_watermarks, .optimize_watermarks = vlv_optimize_watermarks, .atomic_update_watermarks = vlv_atomic_update_fifo, - .get_hw_state = vlv_wm_get_hw_state_and_sanitize, + .get_hw_state = vlv_wm_get_hw_state, + .sanitize = vlv_wm_sanitize, }; static const struct intel_wm_funcs g4x_wm_funcs = { .compute_watermarks = g4x_compute_watermarks, .initial_watermarks = g4x_initial_watermarks, .optimize_watermarks = g4x_optimize_watermarks, - .get_hw_state = g4x_wm_get_hw_state_and_sanitize, + .get_hw_state = g4x_wm_get_hw_state, + .sanitize = g4x_wm_sanitize, }; static const struct intel_wm_funcs pnv_wm_funcs = { diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h b/drivers/gpu/drm/i915/display/intel_display_core.h index 7360ad39b1cc..eeb7ae3eaea8 100644 --- a/drivers/gpu/drm/i915/display/intel_display_core.h +++ b/drivers/gpu/drm/i915/display/intel_display_core.h @@ -91,6 +91,7 @@ struct intel_wm_funcs { struct intel_crtc *crtc); int (*compute_global_watermarks)(struct intel_atomic_state *state); void (*get_hw_state)(struct drm_i915_private *i915); + void (*sanitize)(struct drm_i915_private *i915); }; struct intel_audio_state { diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index 70fad9f89cea..03c0facfad7a 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -991,6 +991,7 @@ void intel_modeset_setup_hw_state(struct drm_i915_private *i915, intel_dpll_sanitize_state(display); intel_wm_get_hw_state(i915); + intel_wm_sanitize(i915); for_each_intel_crtc(&i915->drm, crtc) { struct intel_crtc_state *crtc_state = diff --git a/drivers/gpu/drm/i915/display/intel_wm.c b/drivers/gpu/drm/i915/display/intel_wm.c index d7dc49aecd27..f00f4cfc58e5 100644 --- a/drivers/gpu/drm/i915/display/intel_wm.c +++ b/drivers/gpu/drm/i915/display/intel_wm.c @@ -108,6 +108,12 @@ void intel_wm_get_hw_state(struct drm_i915_private *i915) return i915->display.funcs.wm->get_hw_state(i915); } +void intel_wm_sanitize(struct drm_i915_private *i915) +{ + if (i915->display.funcs.wm->sanitize) + return i915->display.funcs.wm->sanitize(i915); +} + bool intel_wm_plane_visible(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { diff --git a/drivers/gpu/drm/i915/display/intel_wm.h b/drivers/gpu/drm/i915/display/intel_wm.h index e97cdca89a5c..7d3a447054b3 100644 --- a/drivers/gpu/drm/i915/display/intel_wm.h +++ b/drivers/gpu/drm/i915/display/intel_wm.h @@ -25,6 +25,7 @@ void intel_optimize_watermarks(struct intel_atomic_state *state, struct intel_crtc *crtc); int intel_compute_global_watermarks(struct intel_atomic_state *state); void intel_wm_get_hw_state(struct drm_i915_private *i915); +void intel_wm_sanitize(struct drm_i915_private *i915); bool intel_wm_plane_visible(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state); void intel_print_wm_latency(struct drm_i915_private *i915, diff --git a/drivers/gpu/drm/i915/display/skl_watermark.c b/drivers/gpu/drm/i915/display/skl_watermark.c index da7ffcfd9478..0bd7aa3b2877 100644 --- a/drivers/gpu/drm/i915/display/skl_watermark.c +++ b/drivers/gpu/drm/i915/display/skl_watermark.c @@ -3837,10 +3837,8 @@ static void skl_dbuf_sanitize(struct drm_i915_private *i915) } } -static void skl_wm_get_hw_state_and_sanitize(struct drm_i915_private *i915) +static void skl_wm_sanitize(struct drm_i915_private *i915) { - skl_wm_get_hw_state(i915); - skl_mbus_sanitize(i915); skl_dbuf_sanitize(i915); } @@ -4016,7 +4014,8 @@ void intel_wm_state_verify(struct intel_atomic_state *state, static const struct intel_wm_funcs skl_wm_funcs = { .compute_global_watermarks = skl_compute_wm, - .get_hw_state = skl_wm_get_hw_state_and_sanitize, + .get_hw_state = skl_wm_get_hw_state, + .sanitize = skl_wm_sanitize, }; void skl_wm_init(struct drm_i915_private *i915) -- 2.51.0 From 14968c207a0f88c6d21c798af14fb318b90f2eb8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:18 +0200 Subject: [PATCH 08/16] drm/i915: Do wm readout ealier for skl+ MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Move the wm readout to happen earlier. This is needed because the bw_state readout will need ddb information populated by the wm readout. For now limit this to skl+ as I've not really analyzed the implications of doing this on other platforms. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-17-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_modeset_setup.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index 03c0facfad7a..6a4142d7d025 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -880,6 +880,10 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) crtc_state->port_clock); } + /* TODO move here (or even earlier?) on all platforms */ + if (DISPLAY_VER(display) >= 9) + intel_wm_get_hw_state(i915); + intel_bw_update_hw_state(display); intel_cdclk_update_hw_state(display); @@ -990,7 +994,9 @@ void intel_modeset_setup_hw_state(struct drm_i915_private *i915, intel_dpll_sanitize_state(display); - intel_wm_get_hw_state(i915); + /* TODO move earlier on all platforms */ + if (DISPLAY_VER(display) < 9) + intel_wm_get_hw_state(i915); intel_wm_sanitize(i915); for_each_intel_crtc(&i915->drm, crtc) { -- 2.51.0 From e6001c930aae40f676227a27e0674e4c8953b1de Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:19 +0200 Subject: [PATCH 09/16] drm/i915: Move dbuf_state->active_pipes into skl_wm_get_hw_state() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Move the dbuf_state readout parts into skl_wm_get_hw_state() so that the details are better hidden from sight. This will stop updating this on pre-skl, but that's what we want since the dbuf state is only used on skl+. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-18-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_modeset_setup.c | 8 -------- drivers/gpu/drm/i915/display/skl_watermark.c | 5 ++++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index 6a4142d7d025..312b21b1ab59 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -693,8 +693,6 @@ static void readout_plane_state(struct drm_i915_private *i915) static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) { struct intel_display *display = &i915->display; - struct intel_dbuf_state *dbuf_state = - to_intel_dbuf_state(i915->display.dbuf.obj.state); struct intel_pmdemand_state *pmdemand_state = to_intel_pmdemand_state(i915->display.pmdemand.obj.state); enum pipe pipe; @@ -702,7 +700,6 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) struct intel_encoder *encoder; struct intel_connector *connector; struct drm_connector_list_iter conn_iter; - u8 active_pipes = 0; for_each_intel_crtc(&i915->drm, crtc) { struct intel_crtc_state *crtc_state = @@ -719,17 +716,12 @@ static void intel_modeset_readout_hw_state(struct drm_i915_private *i915) crtc->base.enabled = crtc_state->hw.enable; crtc->active = crtc_state->hw.active; - if (crtc_state->hw.active) - active_pipes |= BIT(crtc->pipe); - drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] hw state readout: %s\n", crtc->base.base.id, crtc->base.name, str_enabled_disabled(crtc_state->hw.active)); } - dbuf_state->active_pipes = active_pipes; - readout_plane_state(i915); for_each_intel_encoder(&i915->drm, encoder) { diff --git a/drivers/gpu/drm/i915/display/skl_watermark.c b/drivers/gpu/drm/i915/display/skl_watermark.c index 0bd7aa3b2877..2d0de1c63308 100644 --- a/drivers/gpu/drm/i915/display/skl_watermark.c +++ b/drivers/gpu/drm/i915/display/skl_watermark.c @@ -3157,6 +3157,7 @@ static void skl_wm_get_hw_state(struct drm_i915_private *i915) dbuf_state->joined_mbus = intel_de_read(display, MBUS_CTL) & MBUS_JOIN; dbuf_state->mdclk_cdclk_ratio = intel_mdclk_cdclk_ratio(display, &display->cdclk.hw); + dbuf_state->active_pipes = 0; for_each_intel_crtc(display->drm, crtc) { struct intel_crtc_state *crtc_state = @@ -3168,8 +3169,10 @@ static void skl_wm_get_hw_state(struct drm_i915_private *i915) memset(&crtc_state->wm.skl.optimal, 0, sizeof(crtc_state->wm.skl.optimal)); - if (crtc_state->hw.active) + if (crtc_state->hw.active) { skl_pipe_wm_get_hw_state(crtc, &crtc_state->wm.skl.optimal); + dbuf_state->active_pipes |= BIT(pipe); + } crtc_state->wm.skl.raw = crtc_state->wm.skl.optimal; memset(&dbuf_state->ddb[pipe], 0, sizeof(dbuf_state->ddb[pipe])); -- 2.51.0 From bb800b56f01f430f4d7b360c758a9ed28f758d75 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 6 Mar 2025 18:34:20 +0200 Subject: [PATCH 10/16] drm/i915: Relocate intel_bw_crtc_update() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit intel_bw_crtc_update() is only used by the readout path, so relocate the function next its only caller. Easier to read the code when related things are nearby. Reviewed-by: Vinod Govindapillai Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20250306163420.3961-19-ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index e4d79b496e12..048be2872247 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -806,24 +806,6 @@ static int intel_bw_crtc_min_cdclk(const struct intel_crtc_state *crtc_state) return DIV_ROUND_UP_ULL(mul_u32_u32(intel_bw_crtc_data_rate(crtc_state), 10), 512); } -static void intel_bw_crtc_update(struct intel_bw_state *bw_state, - const struct intel_crtc_state *crtc_state) -{ - struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); - struct drm_i915_private *i915 = to_i915(crtc->base.dev); - - bw_state->data_rate[crtc->pipe] = - intel_bw_crtc_data_rate(crtc_state); - bw_state->num_active_planes[crtc->pipe] = - intel_bw_crtc_num_active_planes(crtc_state); - bw_state->force_check_qgv = true; - - drm_dbg_kms(&i915->drm, "pipe %c data rate %u num active planes %u\n", - pipe_name(crtc->pipe), - bw_state->data_rate[crtc->pipe], - bw_state->num_active_planes[crtc->pipe]); -} - static unsigned int intel_bw_num_active_planes(struct drm_i915_private *dev_priv, const struct intel_bw_state *bw_state) { @@ -1422,6 +1404,24 @@ int intel_bw_atomic_check(struct intel_atomic_state *state) return 0; } +static void intel_bw_crtc_update(struct intel_bw_state *bw_state, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + bw_state->data_rate[crtc->pipe] = + intel_bw_crtc_data_rate(crtc_state); + bw_state->num_active_planes[crtc->pipe] = + intel_bw_crtc_num_active_planes(crtc_state); + bw_state->force_check_qgv = true; + + drm_dbg_kms(&i915->drm, "pipe %c data rate %u num active planes %u\n", + pipe_name(crtc->pipe), + bw_state->data_rate[crtc->pipe], + bw_state->num_active_planes[crtc->pipe]); +} + void intel_bw_update_hw_state(struct intel_display *display) { struct intel_bw_state *bw_state = -- 2.51.0 From eb4796d8625902adfd0bc7226306afcde617f7c9 Mon Sep 17 00:00:00 2001 From: Gustavo Sousa Date: Thu, 27 Feb 2025 16:14:25 -0300 Subject: [PATCH 11/16] drm/i915/xe3lpd: Map POWER_DOMAIN_AUDIO_PLAYBACK to DC_off In Xe3_LPD, display audio has the core audio logic located in PG0 and per-transcoder logic in the same power well that provides power for the transcoder [1]. For stuff like audio device enumeration, we need to ensure that PG0 is turned on. For playback, we additionally need the transcoder's power well to be enabled. That essentially means that, for audio playback, there isn't a special power well that needs to be enabled, because modeset sequences will ensure that the required power wells are enabled. That said, there might be cases where PG0 could be disabled due to display entering DC6 while the audio driver tries to interact with the graphics driver for stuff like audio device enumeration. We recently hit that kind of scenario, where "aplay -l" was being used to enumerate audio devices on a PTL machine with PSR enabled and no external displays attached. Since intel_audio_component_get_power() uses POWER_DOMAIN_AUDIO_PLAYBACK, make sure to map that power domain to DC_off power well, so that we disable dynamic DC states (which includes DC6) while the audio driver needs display audio power. [1] The core-audio vs per-transcoder logic split is not really new in Xe3_LPD. This is also true for previous display generations. We need to figure out the correct version where this split happened so that we can apply fixes in the current power domain mapping. Bspec: 72519 Reviewed-by: Kai Vehmanen Link: https://patchwork.freedesktop.org/patch/msgid/20250227-xe3lpd-power-domain-audio-playback-v1-1-5765f21da977@intel.com Signed-off-by: Gustavo Sousa --- drivers/gpu/drm/i915/display/intel_display_power_map.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/i915/display/intel_display_power_map.c b/drivers/gpu/drm/i915/display/intel_display_power_map.c index e80e1fd611ca..ab1163744bc5 100644 --- a/drivers/gpu/drm/i915/display/intel_display_power_map.c +++ b/drivers/gpu/drm/i915/display/intel_display_power_map.c @@ -1696,6 +1696,7 @@ I915_DECL_PW_DOMAINS(xe3lpd_pwdoms_dc_off, XE3LPD_PW_C_POWER_DOMAINS, XE3LPD_PW_D_POWER_DOMAINS, POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUDIO_PLAYBACK, POWER_DOMAIN_INIT); static const struct i915_power_well_desc xe3lpd_power_wells_dcoff[] = { -- 2.51.0 From 9f1e253d789649745db33a205969169033f078c9 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 4 Mar 2025 17:29:12 +0200 Subject: [PATCH 12/16] drm/i915/hpd: Track HPD pins instead of ports for HPD pulse events MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Track the HPD pin instead of the corresponding encoder ports for pending short/long HPD pulse events. This is how the pending hotplug events are tracked and there is no reason for tracking the pulse events differently. After this change intel_hpd_trigger_irq() will set the short pulse event pending for all encoders using the given HPD pin. This doesn't change the behavior, as atm in case of multiple (2) encoders sharing the same pin only one will have a pulse handler, so for other encoders without a pulse handler the event is ignored. Also setting the pulse event pending for all encoders using the HPD pin is what happens after an actual HPD IRQ, the effect of calling intel_hpd_trigger_irq() should match this. In a following change this also makes it simpler to block the handling of a short/long pulse event on an HPD pin for all the encoders using this HPD pin. Suggested-by: Ville Syrjälä Reviewed-by: Jani Nikula Signed-off-by: Imre Deak Link: https://patchwork.freedesktop.org/patch/msgid/20250304152917.3407080-2-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_display_core.h | 4 +-- drivers/gpu/drm/i915/display/intel_hotplug.c | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h b/drivers/gpu/drm/i915/display/intel_display_core.h index eeb7ae3eaea8..afb2184bf233 100644 --- a/drivers/gpu/drm/i915/display/intel_display_core.h +++ b/drivers/gpu/drm/i915/display/intel_display_core.h @@ -170,8 +170,8 @@ struct intel_hotplug { u32 retry_bits; struct delayed_work reenable_work; - u32 long_port_mask; - u32 short_port_mask; + u32 long_hpd_pin_mask; + u32 short_hpd_pin_mask; struct work_struct dig_port_work; struct work_struct poll_init_work; diff --git a/drivers/gpu/drm/i915/display/intel_hotplug.c b/drivers/gpu/drm/i915/display/intel_hotplug.c index 00d7b1ccf190..9692b5c01aea 100644 --- a/drivers/gpu/drm/i915/display/intel_hotplug.c +++ b/drivers/gpu/drm/i915/display/intel_hotplug.c @@ -353,28 +353,28 @@ static void i915_digport_work_func(struct work_struct *work) { struct drm_i915_private *dev_priv = container_of(work, struct drm_i915_private, display.hotplug.dig_port_work); - u32 long_port_mask, short_port_mask; + u32 long_hpd_pin_mask, short_hpd_pin_mask; struct intel_encoder *encoder; u32 old_bits = 0; spin_lock_irq(&dev_priv->irq_lock); - long_port_mask = dev_priv->display.hotplug.long_port_mask; - dev_priv->display.hotplug.long_port_mask = 0; - short_port_mask = dev_priv->display.hotplug.short_port_mask; - dev_priv->display.hotplug.short_port_mask = 0; + long_hpd_pin_mask = dev_priv->display.hotplug.long_hpd_pin_mask; + dev_priv->display.hotplug.long_hpd_pin_mask = 0; + short_hpd_pin_mask = dev_priv->display.hotplug.short_hpd_pin_mask; + dev_priv->display.hotplug.short_hpd_pin_mask = 0; spin_unlock_irq(&dev_priv->irq_lock); for_each_intel_encoder(&dev_priv->drm, encoder) { struct intel_digital_port *dig_port; - enum port port = encoder->port; + enum hpd_pin pin = encoder->hpd_pin; bool long_hpd, short_hpd; enum irqreturn ret; if (!intel_encoder_has_hpd_pulse(encoder)) continue; - long_hpd = long_port_mask & BIT(port); - short_hpd = short_port_mask & BIT(port); + long_hpd = long_hpd_pin_mask & BIT(pin); + short_hpd = short_hpd_pin_mask & BIT(pin); if (!long_hpd && !short_hpd) continue; @@ -384,7 +384,7 @@ static void i915_digport_work_func(struct work_struct *work) ret = dig_port->hpd_pulse(dig_port, long_hpd); if (ret == IRQ_NONE) { /* fall back to old school hpd */ - old_bits |= BIT(encoder->hpd_pin); + old_bits |= BIT(pin); } } @@ -407,9 +407,10 @@ static void i915_digport_work_func(struct work_struct *work) void intel_hpd_trigger_irq(struct intel_digital_port *dig_port) { struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + struct intel_encoder *encoder = &dig_port->base; spin_lock_irq(&i915->irq_lock); - i915->display.hotplug.short_port_mask |= BIT(dig_port->base.port); + i915->display.hotplug.short_hpd_pin_mask |= BIT(encoder->hpd_pin); spin_unlock_irq(&i915->irq_lock); queue_work(i915->display.hotplug.dp_wq, &i915->display.hotplug.dig_port_work); @@ -557,7 +558,6 @@ void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, * only the one of them (DP) will have ->hpd_pulse(). */ for_each_intel_encoder(&dev_priv->drm, encoder) { - enum port port = encoder->port; bool long_hpd; pin = encoder->hpd_pin; @@ -577,10 +577,10 @@ void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, if (long_hpd) { long_hpd_pulse_mask |= BIT(pin); - dev_priv->display.hotplug.long_port_mask |= BIT(port); + dev_priv->display.hotplug.long_hpd_pin_mask |= BIT(pin); } else { short_hpd_pulse_mask |= BIT(pin); - dev_priv->display.hotplug.short_port_mask |= BIT(port); + dev_priv->display.hotplug.short_hpd_pin_mask |= BIT(pin); } } @@ -920,8 +920,8 @@ void intel_hpd_cancel_work(struct drm_i915_private *dev_priv) spin_lock_irq(&dev_priv->irq_lock); - dev_priv->display.hotplug.long_port_mask = 0; - dev_priv->display.hotplug.short_port_mask = 0; + dev_priv->display.hotplug.long_hpd_pin_mask = 0; + dev_priv->display.hotplug.short_hpd_pin_mask = 0; dev_priv->display.hotplug.event_bits = 0; dev_priv->display.hotplug.retry_bits = 0; -- 2.51.0 From 4b16619608ff14338b6001acb810506079c49749 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 4 Mar 2025 17:29:13 +0200 Subject: [PATCH 13/16] drm/i915/hpd: Let an HPD pin be in the disabled state when handling missed IRQs After suspending and resuming the detection on connectors, HPD IRQs that arrived while the detection was suspended, are handled by scheduling the intel_hotplug::hotplug work for them. All HPD pins must be at this point in either the HPD_ENABLED (set for all pins during driver loading/system resuming) or HPD_MARK_DISABLED (set by IRQ storm detection) state: the HPD_DISABLED state for a pin can be set only from the HPD_MARK_DISABLED state by the hotplug work after a storm detection (enabling polling on the given pin/connector), however the hotplug work won't be scheduled while the detection is suspended. A follow-up change will add support for blocking the HPD IRQ handling on a given HPD pin (without disabling the IRQ generation on it), after which it becomes possible to see a pin in the HPD_DISABLED state when unblocking the IRQ handling (since the blocking could've happened for an already disabled pin). Adjust queue_work_for_missed_irqs() accordingly, so that this function can be reused for unblocking the IRQ handling. Reviewed-by: Jani Nikula Signed-off-by: Imre Deak Link: https://patchwork.freedesktop.org/patch/msgid/20250304152917.3407080-3-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_hotplug.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/i915/display/intel_hotplug.c b/drivers/gpu/drm/i915/display/intel_hotplug.c index 9692b5c01aea..3fb5feeefa14 100644 --- a/drivers/gpu/drm/i915/display/intel_hotplug.c +++ b/drivers/gpu/drm/i915/display/intel_hotplug.c @@ -980,6 +980,7 @@ static void queue_work_for_missed_irqs(struct drm_i915_private *i915) case HPD_MARK_DISABLED: queue_work = true; break; + case HPD_DISABLED: case HPD_ENABLED: break; default: -- 2.51.0 From 0d77a3e0ea90a7ee25755a94694cdfd822c9db6b Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 5 Mar 2025 13:48:19 +0200 Subject: [PATCH 14/16] drm/i915/hpd: Add support for blocking the IRQ handling on an HPD pin MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Add support for blocking the IRQ handling on the HPD pin of a given encoder, handling IRQs that arrived while in the blocked state after unblocking the IRQ handling. This will be used by a follow-up change, which blocks/unblocks the IRQ handling around DP link training. This is similar to the intel_hpd_disable/enable() functionality, by also handling encoders/ports with a pulse handler (i.e. also blocking/unblocking the short/long pulse handling) and handling the IRQs arrived in the blocked state after the handling is unblocked (vs. just dropping such IRQs). v2: - Handle encoders without a port assigned to them. - Fix clearing IRQs from intel_hotplug::short_port_mask. v3: - Rename intel_hpd_suspend/resume() to intel_hpd_block/unblock(). (Jani) - Refer to HPD pins as hpd_pin vs. hpd. - Flush dig_port_work in intel_hpd_block() if any encoder using the HPD pin has a pulse handler. v4: - Fix hpd_pin_has_pulse(), checking the encoder's HPD pin. v5: - Rebase on port->hpd_pin tracking. (Ville) v6: (Jani) - Add hpd_pin_is_blocked() helper. - Use the hpd_pin_mask term for a mask of pins instead of hpd_pins. - Prevent decrementing a 0 refcount in unblock_hpd_pin(). Cc: Jani Nikula Cc: Ville Syrjälä Reviewed-by: Jani Nikula Signed-off-by: Imre Deak Link: https://patchwork.freedesktop.org/patch/msgid/20250305114820.3523077-1-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_display_core.h | 1 + drivers/gpu/drm/i915/display/intel_hotplug.c | 210 +++++++++++++++--- drivers/gpu/drm/i915/display/intel_hotplug.h | 2 + 3 files changed, 188 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h b/drivers/gpu/drm/i915/display/intel_display_core.h index afb2184bf233..3673275f9061 100644 --- a/drivers/gpu/drm/i915/display/intel_display_core.h +++ b/drivers/gpu/drm/i915/display/intel_display_core.h @@ -160,6 +160,7 @@ struct intel_hotplug { struct { unsigned long last_jiffies; int count; + int blocked_count; enum { HPD_ENABLED = 0, HPD_DISABLED = 1, diff --git a/drivers/gpu/drm/i915/display/intel_hotplug.c b/drivers/gpu/drm/i915/display/intel_hotplug.c index 3fb5feeefa14..94b4dcf10f58 100644 --- a/drivers/gpu/drm/i915/display/intel_hotplug.c +++ b/drivers/gpu/drm/i915/display/intel_hotplug.c @@ -349,19 +349,62 @@ static bool intel_encoder_has_hpd_pulse(struct intel_encoder *encoder) enc_to_dig_port(encoder)->hpd_pulse != NULL; } +static bool hpd_pin_has_pulse(struct intel_display *display, enum hpd_pin pin) +{ + struct intel_encoder *encoder; + + for_each_intel_encoder(display->drm, encoder) { + if (encoder->hpd_pin != pin) + continue; + + if (intel_encoder_has_hpd_pulse(encoder)) + return true; + } + + return false; +} + +static bool hpd_pin_is_blocked(struct intel_display *display, enum hpd_pin pin) +{ + struct drm_i915_private *i915 = to_i915(display->drm); + + lockdep_assert_held(&i915->irq_lock); + + return display->hotplug.stats[pin].blocked_count; +} + +static u32 get_blocked_hpd_pin_mask(struct intel_display *display) +{ + enum hpd_pin pin; + u32 hpd_pin_mask = 0; + + for_each_hpd_pin(pin) { + if (hpd_pin_is_blocked(display, pin)) + hpd_pin_mask |= BIT(pin); + } + + return hpd_pin_mask; +} + static void i915_digport_work_func(struct work_struct *work) { - struct drm_i915_private *dev_priv = - container_of(work, struct drm_i915_private, display.hotplug.dig_port_work); + struct intel_display *display = + container_of(work, struct intel_display, hotplug.dig_port_work); + struct drm_i915_private *dev_priv = to_i915(display->drm); + struct intel_hotplug *hotplug = &display->hotplug; u32 long_hpd_pin_mask, short_hpd_pin_mask; struct intel_encoder *encoder; + u32 blocked_hpd_pin_mask; u32 old_bits = 0; spin_lock_irq(&dev_priv->irq_lock); - long_hpd_pin_mask = dev_priv->display.hotplug.long_hpd_pin_mask; - dev_priv->display.hotplug.long_hpd_pin_mask = 0; - short_hpd_pin_mask = dev_priv->display.hotplug.short_hpd_pin_mask; - dev_priv->display.hotplug.short_hpd_pin_mask = 0; + + blocked_hpd_pin_mask = get_blocked_hpd_pin_mask(display); + long_hpd_pin_mask = hotplug->long_hpd_pin_mask & ~blocked_hpd_pin_mask; + hotplug->long_hpd_pin_mask &= ~long_hpd_pin_mask; + short_hpd_pin_mask = hotplug->short_hpd_pin_mask & ~blocked_hpd_pin_mask; + hotplug->short_hpd_pin_mask &= ~short_hpd_pin_mask; + spin_unlock_irq(&dev_priv->irq_lock); for_each_intel_encoder(&dev_priv->drm, encoder) { @@ -406,14 +449,18 @@ static void i915_digport_work_func(struct work_struct *work) */ void intel_hpd_trigger_irq(struct intel_digital_port *dig_port) { - struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + struct intel_display *display = to_intel_display(dig_port); + struct drm_i915_private *i915 = to_i915(display->drm); + struct intel_hotplug *hotplug = &display->hotplug; struct intel_encoder *encoder = &dig_port->base; spin_lock_irq(&i915->irq_lock); - i915->display.hotplug.short_hpd_pin_mask |= BIT(encoder->hpd_pin); - spin_unlock_irq(&i915->irq_lock); - queue_work(i915->display.hotplug.dp_wq, &i915->display.hotplug.dig_port_work); + hotplug->short_hpd_pin_mask |= BIT(encoder->hpd_pin); + if (!hpd_pin_is_blocked(display, encoder->hpd_pin)) + queue_work(hotplug->dp_wq, &hotplug->dig_port_work); + + spin_unlock_irq(&i915->irq_lock); } /* @@ -421,9 +468,10 @@ void intel_hpd_trigger_irq(struct intel_digital_port *dig_port) */ static void i915_hotplug_work_func(struct work_struct *work) { - struct drm_i915_private *dev_priv = - container_of(work, struct drm_i915_private, - display.hotplug.hotplug_work.work); + struct intel_display *display = + container_of(work, struct intel_display, hotplug.hotplug_work.work); + struct drm_i915_private *dev_priv = to_i915(display->drm); + struct intel_hotplug *hotplug = &display->hotplug; struct drm_connector_list_iter conn_iter; struct intel_connector *connector; u32 changed = 0, retry = 0; @@ -431,16 +479,18 @@ static void i915_hotplug_work_func(struct work_struct *work) u32 hpd_retry_bits; struct drm_connector *first_changed_connector = NULL; int changed_connectors = 0; + u32 blocked_hpd_pin_mask; mutex_lock(&dev_priv->drm.mode_config.mutex); drm_dbg_kms(&dev_priv->drm, "running encoder hotplug functions\n"); spin_lock_irq(&dev_priv->irq_lock); - hpd_event_bits = dev_priv->display.hotplug.event_bits; - dev_priv->display.hotplug.event_bits = 0; - hpd_retry_bits = dev_priv->display.hotplug.retry_bits; - dev_priv->display.hotplug.retry_bits = 0; + blocked_hpd_pin_mask = get_blocked_hpd_pin_mask(display); + hpd_event_bits = hotplug->event_bits & ~blocked_hpd_pin_mask; + hotplug->event_bits &= ~hpd_event_bits; + hpd_retry_bits = hotplug->retry_bits & ~blocked_hpd_pin_mask; + hotplug->retry_bits &= ~hpd_retry_bits; /* Enable polling for connectors which had HPD IRQ storms */ intel_hpd_irq_storm_switch_to_polling(dev_priv); @@ -539,6 +589,7 @@ static void i915_hotplug_work_func(struct work_struct *work) void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, u32 pin_mask, u32 long_mask) { + struct intel_display *display = to_intel_display(&dev_priv->drm); struct intel_encoder *encoder; bool storm_detected = false; bool queue_dig = false, queue_hp = false; @@ -573,7 +624,9 @@ void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, "digital hpd on [ENCODER:%d:%s] - %s\n", encoder->base.base.id, encoder->base.name, long_hpd ? "long" : "short"); - queue_dig = true; + + if (!hpd_pin_is_blocked(display, pin)) + queue_dig = true; if (long_hpd) { long_hpd_pulse_mask |= BIT(pin); @@ -617,7 +670,9 @@ void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, } else { dev_priv->display.hotplug.event_bits |= BIT(pin); long_hpd = true; - queue_hp = true; + + if (!hpd_pin_is_blocked(display, pin)) + queue_hp = true; } if (intel_hpd_irq_storm_detect(dev_priv, pin, long_hpd)) { @@ -915,11 +970,15 @@ static bool cancel_all_detection_work(struct drm_i915_private *i915) void intel_hpd_cancel_work(struct drm_i915_private *dev_priv) { + struct intel_display *display = to_intel_display(&dev_priv->drm); + if (!HAS_DISPLAY(dev_priv)) return; spin_lock_irq(&dev_priv->irq_lock); + drm_WARN_ON(display->drm, get_blocked_hpd_pin_mask(display)); + dev_priv->display.hotplug.long_hpd_pin_mask = 0; dev_priv->display.hotplug.short_hpd_pin_mask = 0; dev_priv->display.hotplug.event_bits = 0; @@ -966,19 +1025,22 @@ void intel_hpd_enable(struct drm_i915_private *dev_priv, enum hpd_pin pin) static void queue_work_for_missed_irqs(struct drm_i915_private *i915) { - bool queue_work = false; + struct intel_display *display = to_intel_display(&i915->drm); + struct intel_hotplug *hotplug = &display->hotplug; + bool queue_hp_work = false; + u32 blocked_hpd_pin_mask; enum hpd_pin pin; lockdep_assert_held(&i915->irq_lock); - if (i915->display.hotplug.event_bits || - i915->display.hotplug.retry_bits) - queue_work = true; + blocked_hpd_pin_mask = get_blocked_hpd_pin_mask(display); + if ((hotplug->event_bits | hotplug->retry_bits) & ~blocked_hpd_pin_mask) + queue_hp_work = true; for_each_hpd_pin(pin) { switch (i915->display.hotplug.stats[pin].state) { case HPD_MARK_DISABLED: - queue_work = true; + queue_hp_work = true; break; case HPD_DISABLED: case HPD_ENABLED: @@ -988,10 +1050,108 @@ static void queue_work_for_missed_irqs(struct drm_i915_private *i915) } } - if (queue_work) + if ((hotplug->long_hpd_pin_mask | hotplug->short_hpd_pin_mask) & ~blocked_hpd_pin_mask) + queue_work(hotplug->dp_wq, &hotplug->dig_port_work); + + if (queue_hp_work) queue_delayed_detection_work(i915, &i915->display.hotplug.hotplug_work, 0); } +static bool block_hpd_pin(struct intel_display *display, enum hpd_pin pin) +{ + struct drm_i915_private *i915 = to_i915(display->drm); + struct intel_hotplug *hotplug = &display->hotplug; + + lockdep_assert_held(&i915->irq_lock); + + hotplug->stats[pin].blocked_count++; + + return hotplug->stats[pin].blocked_count == 1; +} + +static bool unblock_hpd_pin(struct intel_display *display, enum hpd_pin pin) +{ + struct drm_i915_private *i915 = to_i915(display->drm); + struct intel_hotplug *hotplug = &display->hotplug; + + lockdep_assert_held(&i915->irq_lock); + + if (drm_WARN_ON(display->drm, hotplug->stats[pin].blocked_count == 0)) + return true; + + hotplug->stats[pin].blocked_count--; + + return hotplug->stats[pin].blocked_count == 0; +} + +/** + * intel_hpd_block - Block handling of HPD IRQs on an HPD pin + * @encoder: Encoder to block the HPD handling for + * + * Blocks the handling of HPD IRQs on the HPD pin of @encoder. + * + * On return: + * - It's guaranteed that the blocked encoders' HPD pulse handler + * (via intel_digital_port::hpd_pulse()) is not running. + * - The hotplug event handling (via intel_encoder::hotplug()) of an + * HPD IRQ pending at the time this function is called may be still + * running. + * - Detection on the encoder's connector (via + * drm_connector_helper_funcs::detect_ctx(), + * drm_connector_funcs::detect()) remains allowed, for instance as part of + * userspace connector probing, or DRM core's connector polling. + * + * The call must be followed by calling intel_hpd_unblock(). + * + * Note that the handling of HPD IRQs for another encoder using the same HPD + * pin as that of @encoder will be also blocked. + */ +void intel_hpd_block(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct drm_i915_private *i915 = to_i915(display->drm); + struct intel_hotplug *hotplug = &display->hotplug; + bool do_flush = false; + + if (encoder->hpd_pin == HPD_NONE) + return; + + spin_lock_irq(&i915->irq_lock); + + if (block_hpd_pin(display, encoder->hpd_pin)) + do_flush = true; + + spin_unlock_irq(&i915->irq_lock); + + if (do_flush && hpd_pin_has_pulse(display, encoder->hpd_pin)) + flush_work(&hotplug->dig_port_work); +} + +/** + * intel_hpd_unblock - Unblock handling of HPD IRQs on an HPD pin + * @encoder: Encoder to unblock the HPD handling for + * + * Unblock the handling of HPD IRQs on the HPD pin of @encoder, which was + * previously blocked by intel_hpd_block(). Any HPD IRQ raised on the + * HPD pin while it was blocked will be handled for @encoder and for any + * other encoder sharing the same HPD pin. + */ +void intel_hpd_unblock(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct drm_i915_private *i915 = to_i915(display->drm); + + if (encoder->hpd_pin == HPD_NONE) + return; + + spin_lock_irq(&i915->irq_lock); + + if (unblock_hpd_pin(display, encoder->hpd_pin)) + queue_work_for_missed_irqs(i915); + + spin_unlock_irq(&i915->irq_lock); +} + void intel_hpd_enable_detection_work(struct drm_i915_private *i915) { spin_lock_irq(&i915->irq_lock); diff --git a/drivers/gpu/drm/i915/display/intel_hotplug.h b/drivers/gpu/drm/i915/display/intel_hotplug.h index d6986902b054..5f9857136f5e 100644 --- a/drivers/gpu/drm/i915/display/intel_hotplug.h +++ b/drivers/gpu/drm/i915/display/intel_hotplug.h @@ -28,6 +28,8 @@ void intel_hpd_cancel_work(struct drm_i915_private *dev_priv); enum hpd_pin intel_hpd_pin_default(enum port port); bool intel_hpd_disable(struct drm_i915_private *dev_priv, enum hpd_pin pin); void intel_hpd_enable(struct drm_i915_private *dev_priv, enum hpd_pin pin); +void intel_hpd_block(struct intel_encoder *encoder); +void intel_hpd_unblock(struct intel_encoder *encoder); void intel_hpd_debugfs_register(struct drm_i915_private *i915); void intel_hpd_enable_detection_work(struct drm_i915_private *i915); -- 2.51.0 From 35021b5b15de0c4eceecda9e2dadab2e5e56b7e2 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 4 Mar 2025 17:29:15 +0200 Subject: [PATCH 15/16] drm/i915/dp: Fix link training interrupted by a short HPD pulse During Display Port link training the handling of HPD pulses should be prevented, as that handling can interfere with the link training: - Accessing DPCD registers outside the range of link training registers are not allowed by the Standard (see DP Standard v2.1, 3.5.2.16.1, 3.6.6.1). The pulse handler reads the DPRX capability registers, which are outside of the allowed range. - Switching of the LTTPR transparent/non-transparent mode may reset the LTTPRs on the link, thus aborting any ongoing link training. The pulse handler does set the LTTPR mode, thus it could unexpectedly abort the ongoing link training. Block/unblock the HPD pulse handling for the duration of the link training to prevent the above DPCD register accesses / LTTPR mode change. Apart from the above scenarios, there are other ways a non-link training DPCD register could be accessed during link training: via the DRM AUX device node, or via DPCD register probing (as performed by drm_dp_dpcd_probe()). These will be addressed by a follow-up change. v2: Rebase on the intel_hpd_suspend/resume -> intel_hpd_block/unblock() rename change. Reviewed-by: Jani Nikula Signed-off-by: Imre Deak Link: https://patchwork.freedesktop.org/patch/msgid/20250304152917.3407080-5-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp_link_training.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index 581f1dab618e..5d549ac4de1c 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -1124,6 +1124,8 @@ intel_dp_128b132b_intra_hop(struct intel_dp *intel_dp, void intel_dp_stop_link_train(struct intel_dp *intel_dp, const struct intel_crtc_state *crtc_state) { + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + intel_dp->link_trained = true; intel_dp_disable_dpcd_training_pattern(intel_dp, DP_PHY_DPRX); @@ -1134,6 +1136,8 @@ void intel_dp_stop_link_train(struct intel_dp *intel_dp, wait_for(intel_dp_128b132b_intra_hop(intel_dp, crtc_state) == 0, 500)) { lt_dbg(intel_dp, DP_PHY_DPRX, "128b/132b intra-hop not clearing\n"); } + + intel_hpd_unblock(encoder); } static bool @@ -1616,7 +1620,11 @@ void intel_dp_start_link_train(struct intel_atomic_state *state, * non-transparent mode. During an earlier LTTPR detection this * could've been prevented by an active link. */ - int lttpr_count = intel_dp_init_lttpr_and_dprx_caps(intel_dp); + int lttpr_count; + + intel_hpd_block(encoder); + + lttpr_count = intel_dp_init_lttpr_and_dprx_caps(intel_dp); if (lttpr_count < 0) /* Still continue with enabling the port and link training. */ -- 2.51.0 From 29c09cf200f736138707857696d1f6db2db0299b Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 5 Mar 2025 13:48:20 +0200 Subject: [PATCH 16/16] drm/i915/dp: Queue a link check after link training is complete After link training - both in case of a passing and failing LT result - a work is scheduled to check the link state. This check should take place after the link training is completed by disabling the link training pattern and setting intel_dp::link_trained=true. Atm, the work is scheduled before these steps, which may result in checking the link state too early (and thus not retraining the link as expected). Fix the above by scheduling the link check work after link training is complete. v2: - Add MAX_SEQ_TRAIN_FAILURES instead of open-coding it. (Jani) Reviewed-by: Jani Nikula Signed-off-by: Imre Deak Link: https://patchwork.freedesktop.org/patch/msgid/20250305114820.3523077-2-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_training.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index 5d549ac4de1c..ded246bbf232 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -56,6 +56,8 @@ lt_dbg(_intel_dp, _dp_phy, "Sink disconnected: " _format, ## __VA_ARGS__); \ } while (0) +#define MAX_SEQ_TRAIN_FAILURES 2 + static void intel_dp_reset_lttpr_common_caps(struct intel_dp *intel_dp) { memset(intel_dp->lttpr_common_caps, 0, sizeof(intel_dp->lttpr_common_caps)); @@ -1124,6 +1126,7 @@ intel_dp_128b132b_intra_hop(struct intel_dp *intel_dp, void intel_dp_stop_link_train(struct intel_dp *intel_dp, const struct intel_crtc_state *crtc_state) { + struct intel_display *display = to_intel_display(intel_dp); struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; intel_dp->link_trained = true; @@ -1138,6 +1141,13 @@ void intel_dp_stop_link_train(struct intel_dp *intel_dp, } intel_hpd_unblock(encoder); + + if (!display->hotplug.ignore_long_hpd && + intel_dp->link.seq_train_failures < MAX_SEQ_TRAIN_FAILURES) { + int delay_ms = intel_dp->link.seq_train_failures ? 0 : 2000; + + intel_encoder_link_check_queue_work(encoder, delay_ms); + } } static bool @@ -1642,7 +1652,6 @@ void intel_dp_start_link_train(struct intel_atomic_state *state, lt_dbg(intel_dp, DP_PHY_DPRX, "Forcing link training failure\n"); } else if (passed) { intel_dp->link.seq_train_failures = 0; - intel_encoder_link_check_queue_work(encoder, 2000); return; } @@ -1665,10 +1674,8 @@ void intel_dp_start_link_train(struct intel_atomic_state *state, return; } - if (intel_dp->link.seq_train_failures < 2) { - intel_encoder_link_check_queue_work(encoder, 0); + if (intel_dp->link.seq_train_failures < MAX_SEQ_TRAIN_FAILURES) return; - } if (intel_dp_schedule_fallback_link_training(state, intel_dp, crtc_state)) return; -- 2.51.0