[PATCH 3/7] drm/i915/vrr: Leave the transcoder to the joiner primary
From: ElXreno
Date: Fri Nov 13 2026 - 20:44:26 EST
All joined pipes run off the transcoder of the primary, and the crtc
states of the secondaries are copies of the primary's state, the VRR
state included. The commit therefore reaches the transcoder level VRR
functions for every joined pipe: intel_pre_update_crtc() calls
intel_vrr_set_transcoder_timings() for each crtc whose VRR parameters
changed, intel_pre_plane_update() and commit_pipe_post_planes() call
intel_vrr_disable() and intel_vrr_enable(), and both commit paths
call intel_vrr_send_push() and intel_vrr_check_push_sent() per crtc.
Do these once, from the primary. When disabling VRR also switch the
active timings of all joined pipes as soon as the transcoder has left
VRR mode, rather than leaving the secondaries on the VRR timings
until their own turn comes.
The check in intel_vrr_set_transcoder_timings() drops the second,
identical programming of the transcoder that a joined fixed refresh
rate configuration gets today. Everything else depends on vrr.enable,
which is never set with the joiner yet, so there is no other
functional change. In particular the push a secondary sends for the
PSR and Panel Replay frame change is left as it is.
The checks in intel_vrr_set_transcoder_timings(), intel_vrr_send_push(),
intel_vrr_enable() and intel_vrr_disable() are the ones Ankit Nautiyal
posted in "drm/i915/vrr: Handle joiner with vrr", last in [1].
[1] https://lore.kernel.org/intel-gfx/20250224061717.1095226-14-ankit.k.nautiyal@xxxxxxxxx/
Suggested-by: Ankit Nautiyal <ankit.k.nautiyal@xxxxxxxxx>
Signed-off-by: ElXreno <elxreno@xxxxxxxxx>
---
drivers/gpu/drm/i915/display/intel_display.c | 15 ++++++++++++--
drivers/gpu/drm/i915/display/intel_vrr.c | 21 ++++++++++++++++++++
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index 9a503b25ae47..20b1103e3de4 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -1237,10 +1237,21 @@ static void intel_pre_plane_update(struct intel_atomic_state *state,
intel_psr_pre_plane_update(state, crtc);
- if (intel_crtc_vrr_disabling(state, crtc)) {
+ /* The joined pipes are handled in one go from the primary */
+ if (intel_crtc_vrr_disabling(state, crtc) &&
+ !intel_crtc_is_joiner_secondary(old_crtc_state)) {
+ struct intel_crtc *pipe_crtc;
+
intel_vrr_disable(old_crtc_state);
intel_vrr_dcb_reset(old_crtc_state, crtc);
- intel_crtc_update_active_timings(old_crtc_state, false);
+
+ for_each_intel_crtc_in_pipe_mask(display, pipe_crtc,
+ intel_crtc_joined_pipe_mask(old_crtc_state)) {
+ const struct intel_crtc_state *old_pipe_crtc_state =
+ intel_atomic_get_old_crtc_state(state, pipe_crtc);
+
+ intel_crtc_update_active_timings(old_pipe_crtc_state, false);
+ }
/*
* VRR is being disabled seamlessly (no modeset, Panel Replay
diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c
index a5a5d11380a4..668c495e7407 100644
--- a/drivers/gpu/drm/i915/display/intel_vrr.c
+++ b/drivers/gpu/drm/i915/display/intel_vrr.c
@@ -19,6 +19,7 @@
#include "intel_de.h"
#include "intel_dip.h"
#include "intel_dip_regs.h"
+#include "intel_display.h"
#include "intel_display_limits.h"
#include "intel_display_regs.h"
#include "intel_display_types.h"
@@ -674,6 +675,10 @@ void intel_vrr_set_transcoder_timings(const struct intel_crtc_state *crtc_state)
if (!HAS_VRR(display))
return;
+ /* The joined pipes drive one transcoder, so program it once. */
+ if (intel_crtc_is_joiner_secondary(crtc_state))
+ return;
+
/*
* Bspec says:
* "(note: VRR needs to be programmed after
@@ -773,6 +778,13 @@ void intel_vrr_send_push(struct intel_dsb *dsb,
if (!crtc_state->vrr.enable && !intel_psr_use_trans_push(crtc_state))
return;
+ /*
+ * A VRR push latches all joined pipes at once, it is up to the
+ * primary to send it once every one of them has been armed.
+ */
+ if (crtc_state->vrr.enable && intel_crtc_is_joiner_secondary(crtc_state))
+ return;
+
if (dsb)
intel_dsb_nonpost_start(dsb);
@@ -793,6 +805,9 @@ void intel_vrr_check_push_sent(struct intel_dsb *dsb,
if (!crtc_state->vrr.enable)
return;
+ if (intel_crtc_is_joiner_secondary(crtc_state))
+ return;
+
/*
* Make sure the push send bit has cleared. This should
* already be the case as long as the caller makes sure
@@ -1005,6 +1020,9 @@ void intel_vrr_enable(const struct intel_crtc_state *crtc_state)
if (!crtc_state->vrr.enable)
return;
+ if (intel_crtc_is_joiner_secondary(crtc_state))
+ return;
+
intel_vrr_set_vrr_timings(crtc_state);
intel_vrr_enable_dc_balancing(crtc_state);
@@ -1019,6 +1037,9 @@ void intel_vrr_disable(const struct intel_crtc_state *old_crtc_state)
if (!old_crtc_state->vrr.enable)
return;
+ if (intel_crtc_is_joiner_secondary(old_crtc_state))
+ return;
+
if (!intel_vrr_always_use_vrr_tg(display))
intel_vrr_tg_disable(old_crtc_state);
--
2.56.0