mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation
@ 2026-11-05 15:51 Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 01/27] drm: Add VRR state Nicolas Frattaroli
                   ` (26 more replies)
  0 siblings, 27 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

This series defines a proposed new userspace API for specifying frame
rate targets for variable refresh rate (VRR) mechanisms. This includes
specifying both a constrained range in which VRR should operate, as well
as a fixed target, and a new connector property for QMS (Quick Media
Switching) and passive VRR (as in, keeping the display in VRR mode
without doing VRR).

The reason for wanting such an uAPI is mainly to avoid the imperfection
of the real world. By constraining VRR to a limited range or a fixed
point entirely, we get the benefits of judder-free (or low-latency)
presentation for content within an expected range of frame rates,
without dipping into ones that cause visible brightness flicker. The
addition of QMS furthermore allows the connector to signal an upcoming
rate change ahead of time for media playback scenarios, such that
advanced processing algorithms in e.g. TVs don't have to be disabled to
meet the need to be able to present at full rates with no notice.

Aside from the uAPI, there's the EDID parsing and an implementation for
drivers that use the common HDMI state helpers, with it wired up for
Rockchip (tested and developed on RK3588).

The uAPI consists of 4 new CRTC properties for the frame rate
minimum/maximum target, and several new connector properties. Two of the
connector properties are for reporting the display's VRR range to any
client so that they're aware of quirks the kernel chooses to override
the ones in the EDID with, and the third is for explicitly setting a
mode the display should be in (Game, which is default, Off, and Media).

The Media VRR display mode sets HDMI displays to use QMS. Whether or not
a particular display supports QMS can be determined from the EDID, i.e.
the kernel does not act as userspace's EDID parser through a redundant
property for this.

For some background on why the uAPI uses numerator/denominator frame
rate pairs as opposed to frame intervals or microhertz, my slides from
Display Next Hackfest 2026 give an explanation:

https://gitlab.freedesktop.org/-/project/2891/uploads/9db8886701e2598271a8c4c6dc4dc2b1/display_next_hackfest_2026.pdf

In short: for QMS, we have a very limited set of frame rate targets that
signalling is supported for. This includes both rates such as 24 Hz, and
24/1.001 Hz. Making userspace give us a frame rate as a rational
expression rather than as the output of some rounding calculations
spares us the pain and suffering of then having to correctly guess which
frame rate was originally meant as a target with a <0.1% accuracy
requirement.

There is also a set of IGT tests for exercising the new uAPI on actual
hardware:

https://gitlab.freedesktop.org/CounterPillow/igt-gpu-tools/-/tree/vrr-limiter

== Currently known deficiencies as of v2 ==

I've decided to send out a new revision now rather than wait longer, in
order to get more feedback on the approach. There's still some stuff
that isn't quite right or there yet though:

1. The immutable VRR flavour property for userspace isn't in yet, but
   this will be added in the next revision; it's not relevant for the
   general approach.
2. The EMP packet's m_const flag is not generated correctly for QMS.
   Doing more commits skips without making changes to the connector
   state skips the connector check, so it doesn't get updated to tick
   over from m_const=0 to m_const=1 in a QMS tfr change scenario.
3. On my particular hardware, using modes for QMS that require TMDS
   scrambling, with QMS rates that are quite low, results in a black
   picture despite the display entering QMS mode. The IGT test contains
   a workaround for this.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
Changes in v2:
- Make mode_changed on vrr_enabled change a driver-specific
  consideration, and do it for Rockchip after verifying it's needed
- Move CRTC VRR limiter property attachment to separate function, to be
  invoked by driver
- Invoke aforementioned function in Rockchip VOP2 driver for RK3588
- Validate target range completeness in common CRTC atomic_check code
- Move early hdmi_validate_vrr !vrr_enabled bailout to before state
  re-use check
- Move addition of state re-use check's check for vrr target members to
  the patch that actually implements them (d'oh)
- Make sure old_crtc_state in hdmi_validate_vrr is non-null before
  dereferencing it
- Fix KUnit tests not working unless there was a mode_changed on
  vrr_enabled change by explicitly getting a new connector state in each
  test, refactoring all this out into a small function to call in each
- Use guard macro to acquire and release the modeset lock in KUnit
  tests, fixing a leak of a held lock when tests fail
- Move added edids to drm_kunit_edid.c instead of defining them in the
  header
- Add KUnit tests for limiter range incompleteness
- Deduplicate HDMI VRR EDID parsing with what already landed in drm-tip
  and I rebased across by accident. Includes moving from monitor_range
  to hdmi.vrr_cap.vrr_{min,max}
- Minor doc fix for the aforementioned already landed stuff
- Add vrr_display_min/vrr_display_max connector properties, including
  HDMI helper to set them appropriately
- Add vrr_display_mode property for userspace to explicitly control the
  VRR mode a display is in, in order to avoid blanking. Defaults to
  "Game".
- Enable aforementioned connector properties for Rockchip dw_hdmi_qp
- Change qms_enabled to be just another enum value in vrr_display_mode
- Get rid of the VIC field, read that from the avi infoframe in EMP
  generation instead
- Remove the memcpy prior state path in hdmi_validate_vrr
- Redo EMP generation with respect to new mode logic, remove some
  redundancy
- Clear EMP infoframe regardless of sink-side support
- In hdmi_validate_vrr_limits, round up the vtotal check based on
  vtotal remainder
- Support edge case of sinks that only support QMS, and not VRR,
  and extend the QMS tests for this with another EDID
- In drm_crtc_helper_vrr_vsync, make the cur_err >= denom check
  explicitly cast denom (u32) to int after dividing by 2, to avoid
  both the bad u32 promotion of cur_err and the *2 wraparound
- In hdmi_validate_vrr, reset vrr state error accumulator if VRR target
  changes
- In hdmi_validate_vrr, check for NULL old_conn_state before accessing
  it for the QMS tfr byte check
- Drop the patch for disabling VRR in helper_set_config, as it'd regress
  Xorg.
- Link to v1: https://patch.msgid.link/20260921-vrr-limiter-uapi-v1-0-2fcd7d011646@collabora.com

---
Derek Foreman (4):
      video/hdmi: Add VTEM EMP packing
      drm/bridge: Add VTEM EMP support
      drm/connector: hdmi: Add VTEM EMP generation
      drm/bridge: synopsys: Add VTEM EMP support

Nicolas Frattaroli (23):
      drm: Add VRR state
      drm/connector: add vrr_display_mode connector property
      drm/crtc-helper: Add VRR helper functions
      drm/connector: Add drm_display_info_is_vrr_capable
      drm: Add VRR target frame rate properties
      drm/connector: Add vrr_display_{min,max} connector properties
      drm: Implement VRR rate limiting
      drm/rockchip: dw_hdmi_qp: Add VRR support
      drm/rockchip: Set mode_changed on vrr_enabled change
      drm/rockchip: vop2: Enable VRR
      drm/edid: Parse QMS flag from HDMI SCDS
      drm/edid: Parse QMS TFR min/max flags from HDMI SCDS
      drm/connector: Add "Media" VRR display mode for QMS
      video/hdmi: Add support for QMS in VTEM EMP packing
      drm/connector: hdmi: Add QMS to VTEM EMP generation
      drm/connector: hdmi: Add QMS state validation and computation
      drm/rockchip: dw_hdmi_qp: Add QMS support
      drm/tests: hdmi: Add "Game Mode" VRR tests
      drm/tests: hdmi: Add Fixed/Constrained rate VRR tests
      drm/tests: hdmi: Add Quick Media Switching tests
      drm/connector: Fix docs for cnmvrr and cinemavrr members
      drm/debugfs: Expose HDMI VRR in vrr_range debugfs
      drm/tests: hdmi: Add tests for vrr_display_{min,max} properties

 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c       |   50 +-
 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h       |    1 +
 drivers/gpu/drm/display/drm_bridge_connector.c     |   41 +
 drivers/gpu/drm/display/drm_hdmi_helper.c          |   55 +
 drivers/gpu/drm/display/drm_hdmi_state_helper.c    |  488 ++++++++
 drivers/gpu/drm/drm_atomic.c                       |   14 +
 drivers/gpu/drm/drm_atomic_uapi.c                  |   28 +
 drivers/gpu/drm/drm_connector.c                    |  217 ++++
 drivers/gpu/drm/drm_crtc.c                         |   34 +
 drivers/gpu/drm/drm_crtc_helper.c                  |   93 ++
 drivers/gpu/drm/drm_debugfs.c                      |   24 +-
 drivers/gpu/drm/drm_edid.c                         |    7 +
 drivers/gpu/drm/drm_mode_config.c                  |   20 +
 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c     |   33 +
 drivers/gpu/drm/rockchip/rockchip_drm_fb.c         |   20 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.c       |   65 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.h       |   10 +
 drivers/gpu/drm/rockchip/rockchip_vop2_reg.c       |    4 +
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 1308 ++++++++++++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.c             |  420 +++++++
 drivers/gpu/drm/tests/drm_kunit_edid.h             |    3 +
 drivers/video/hdmi.c                               |   99 +-
 include/drm/display/drm_hdmi_helper.h              |    3 +
 include/drm/drm_bridge.h                           |   29 +
 include/drm/drm_connector.h                        |  107 +-
 include/drm/drm_crtc.h                             |   19 +
 include/drm/drm_crtc_helper.h                      |    6 +
 include/drm/drm_edid.h                             |    3 +
 include/drm/drm_mode_config.h                      |   45 +
 include/linux/hdmi.h                               |   21 +
 30 files changed, 3245 insertions(+), 22 deletions(-)
---
base-commit: 0e8b43b5a5ef2dd52c454158e7fd8a927d796eb4
change-id: 20260919-vrr-limiter-uapi-c99ed93fe7d2

Best regards,
--  
Nicolas Frattaroli <nicolas.frattaroli@collabora.com>


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 01/27] drm: Add VRR state
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 02/27] drm/connector: add vrr_display_mode connector property Nicolas Frattaroli
                   ` (25 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add preliminary state tracking for VRR, and VRR state validation. This
will be used for generating VTEM infoframes in order to support variable
refresh rate functionality.

Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 208 ++++++++++++++++++++++++
 include/drm/drm_crtc.h                          |  10 ++
 2 files changed, 218 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index c63d439382bb..eb6abfd1350d 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -887,6 +887,210 @@ hdmi_generate_infoframes(const struct drm_connector *connector,
 	return 0;
 }
 
+/**
+ * cmp_fraction - compares two fractional numbers
+ * @a: numerator of the first fraction
+ * @b: denominator of the first fraction
+ * @c: numerator of the second fraction
+ * @d: denominator of the second fraction
+ *
+ * Compare fractional expression given by @a divided by @b with fractional
+ * expression given by @c divided by @d, without running into rounding issues.
+ *
+ * Neither @b nor @d should be 0.
+ *
+ * Returns:
+ *  - %1 if @a / @b > @c / @d
+ *  - %-1 if @a / @b < @c / @d
+ *  - %0 if @a / @b == @c / @d
+ *  - %-EDOM if @b is zero or @d is zero.
+ */
+static int cmp_fraction(u32 a, u32 b, u32 c, u32 d)
+{
+	u64 a_d = mul_u32_u32(a, d);
+	u64 b_c = mul_u32_u32(b, c);
+
+	if (WARN_ON(!b || !d))
+		return -EDOM;
+
+	/* a/b > c/d */
+	if (a_d > b_c)
+		return 1;
+
+	/* a/b < c/d */
+	if (a_d < b_c)
+		return -1;
+
+	/* a/b == c/d */
+	return 0;
+}
+
+static int hdmi_calculate_vtotal(const struct drm_display_mode *mode,
+				 u32 rate_n, u32 rate_d, u16 *out_vtotal,
+				 u32 *out_denom, u32 *out_frame_err)
+{
+	u32 denom, result, err;
+	u64 numerator;
+
+	/*
+	 * We perform VRR based rate limiting by adjusting the vertical front
+	 * porch. We do that by picking a new Vtotal to pass to the hardware,
+	 * which extends the display period. Since we can change no other
+	 * timings, this rarely results in a perfect integer match to the
+	 * target framerate. However, we would like to correct for this error
+	 * using only integer math.
+	 *
+	 * For a given refresh rate:
+	 *
+	 * Vtotal =              clock
+	 *          ----------------------------------
+	 *               Htotal * target_refresh
+	 *
+	 * We specify target_refresh as a ratio, because the possible target
+	 * framerates include rates like 23.97... that are actually 24 / 1.001,
+	 * or 24000 / 1001.
+	 *
+	 *                        clock
+	 * Vtotal = ----------------------------------
+	 *            Htotal * target_n / target_d
+	 *
+	 * Some algebra brings us to:
+	 *
+	 *                clock * target_n
+	 * Vtotal = ----------------------------------
+	 *                Htotal * rate_n
+	 *
+	 * The amount of timing error we accumulate every frame will be
+	 * the remainder of that division.
+	 *
+	 * We store the remainder for later. Every frame we add the remainder
+	 * to an accumulator, and when the accumulator exceeds the denominator,
+	 * one Htotal worth of error has accumulated.
+	 *
+	 * In practice, as soon as the accumulated error exceeds half the
+	 * denominator, it is dithered across frames by temporarily extending
+	 * Vtotal by a single row on that frame.
+	 */
+
+	if (check_mul_overflow(mode->crtc_htotal, rate_n, &denom))
+		return -ERANGE;
+
+	if (check_mul_overflow(mode->crtc_clock * 1000ULL, rate_d, &numerator))
+		return -ERANGE;
+
+	result = div_u64_rem(numerator, denom, &err);
+	if (result > U16_MAX)
+		return -ERANGE;
+
+	if (out_vtotal)
+		*out_vtotal = result;
+	if (out_denom)
+		*out_denom = denom;
+	if (out_frame_err)
+		*out_frame_err = err;
+
+	return 0;
+}
+
+static int hdmi_validate_vrr(struct drm_connector *connector,
+			     struct drm_atomic_commit *state)
+{
+	struct drm_connector_state *new_conn_state =
+		drm_atomic_get_new_connector_state(state, connector);
+	struct drm_crtc_state *new_crtc_state =
+		drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	struct drm_crtc_vrr_state *vrr_state =
+		&new_crtc_state->vrr_state;
+	struct drm_display_info *info = &connector->display_info;
+	const struct drm_display_mode *mode = &new_crtc_state->mode;
+	struct drm_device *dev = connector->dev;
+	int mode_refresh, vfront, ret;
+	u32 min_vfreq_n = info->hdmi.vrr_cap.vrr_min;
+	u32 min_vfreq_d = 1;
+	u32 max_vfreq_n = info->hdmi.vrr_cap.vrr_max;
+	u32 max_vfreq_d = 1;
+
+	if (!new_crtc_state->vrr_enabled)
+		return 0;
+
+	if (!info->hdmi.vrr_cap.supported)
+		return -EOPNOTSUPP;
+
+	mode_refresh = drm_mode_vrefresh(mode);
+
+	/* HDMI uses 10 bits to signal the base refresh. */
+	if (mode_refresh < 0 || mode_refresh > 1023) {
+		drm_dbg_kms(dev, "Mode's refresh of %dHz > HDMI VRR maximum (1023)\n",
+			    mode_refresh);
+		return -EINVAL;
+	}
+
+	if (!max_vfreq_n || max_vfreq_n > mode_refresh)
+		max_vfreq_n = mode_refresh;
+
+	if (!min_vfreq_n) {
+		drm_dbg_kms(dev, "Could not get minimum VRR rate from sink\n");
+		return -EINVAL;
+	}
+
+	vfront = mode->crtc_vsync_start - mode->crtc_vdisplay;
+	if (vfront < 0 || vfront > U8_MAX) {
+		drm_dbg_kms(dev, "Vfront of %d would not fit in VTEM packet\n", vfront);
+		return -EINVAL;
+	}
+
+	if (cmp_fraction(min_vfreq_n, min_vfreq_d, max_vfreq_n, max_vfreq_d) > 0) {
+		drm_dbg_kms(dev, "Target max (%u/%u) > target min (%u/%u)\n",
+			    max_vfreq_n, max_vfreq_d, min_vfreq_n, min_vfreq_d);
+		return -EINVAL;
+	}
+
+	ret = hdmi_calculate_vtotal(mode, max_vfreq_n, max_vfreq_d,
+				    &vrr_state->base_vtotal, NULL, NULL);
+	if (ret) {
+		drm_dbg_kms(dev, "Couldn't calculate base_vtotal: %pe\n", ERR_PTR(ret));
+		return ret;
+	}
+
+	/*
+	 * Even at its fastest, it can't go faster than the mode, so
+	 * clamp to avoid imprecisely rounded mode_refresh values we
+	 * can't do anything about from ruining our day.
+	 */
+	vrr_state->base_vtotal = max(vrr_state->base_vtotal, mode->crtc_vtotal);
+
+	if (cmp_fraction(min_vfreq_n, min_vfreq_d, mode_refresh, 1) >= 0) {
+		/* refresh <= VRR min, don't do VRR vtotal adjustment */
+		vrr_state->max_vtotal = mode->crtc_vtotal;
+	} else {
+		/*
+		 * Allow additional front porch until effective rate == VRR min
+		 * In essence, this is mode->vtotal * mode_refresh / min_vfreq
+		 * but the relevant factors factored out from drm_mode_vrefresh()
+		 * to avoid working with rounded values, as well as using the
+		 * hardware adjusted crtc_* values instead.
+		 */
+		ret = hdmi_calculate_vtotal(mode, min_vfreq_n, min_vfreq_d,
+					    &vrr_state->max_vtotal, NULL, NULL);
+		if (ret) {
+			drm_dbg_kms(dev, "Couldn't calculate max_vtotal: %pe\n", ERR_PTR(ret));
+			return ret;
+		}
+
+		if (vrr_state->max_vtotal < mode->crtc_vtotal) {
+			drm_dbg_kms(dev, "max_vtotal=%u < crtc_vtotal=%u\n",
+				    vrr_state->max_vtotal, mode->crtc_vtotal);
+			return -EINVAL;
+		}
+	}
+
+	drm_dbg_kms(dev, "VRR has base_vtotal=%u max_vtotal=%u from %u/%uHz <= rate <= %u/%uHz\n",
+		    vrr_state->base_vtotal, vrr_state->max_vtotal, min_vfreq_n,
+		    min_vfreq_d, max_vfreq_n, max_vfreq_d);
+
+	return 0;
+}
+
 /**
  * drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state
  * @connector: DRM Connector
@@ -919,6 +1123,10 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
 
 	new_conn_state->hdmi.is_limited_range = hdmi_is_limited_range(connector, new_conn_state);
 
+	ret = hdmi_validate_vrr(connector, state);
+	if (ret)
+		return ret;
+
 	ret = hdmi_generate_infoframes(connector, new_conn_state);
 	if (ret)
 		return ret;
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index be5dca538d87..bfb2b0741625 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -63,6 +63,13 @@ struct drm_atomic_commit;
 struct drm_crtc_helper_funcs;
 struct drm_plane_helper_funcs;
 
+struct drm_crtc_vrr_state {
+	u16 cur_vtotal;
+	u16 max_vtotal;
+	u16 base_vtotal;
+	bool dynamic;
+};
+
 /**
  * struct drm_crtc_state - mutable CRTC state
  *
@@ -397,6 +404,9 @@ struct drm_crtc_state {
 	 */
 	struct drm_pending_vblank_event *event;
 
+	/** @vrr_state: State related to variable refresh rate. */
+	struct drm_crtc_vrr_state vrr_state;
+
 	/**
 	 * @commit:
 	 *

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 02/27] drm/connector: add vrr_display_mode connector property
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 01/27] drm: Add VRR state Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 03/27] video/hdmi: Add VTEM EMP packing Nicolas Frattaroli
                   ` (24 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

It's common to want to keep a VRR-capable display in a specific VRR mode
regardless of whether a CRTC uses VRR presentation, to avoid screen
blanking during switching of the VRR mode. On the other hand, it's also
not always desired to keep a display in VRR mode even when nothing does
VRR presentation, for example in battery-powered systems.

To let userspace control this explicitly, instead of relying on implicit
driver policy, introduce a new connector KMS property: vrr_display_mode.

It defaults to "Game", which matches the behaviour of dGPU drivers,
wherein a VRR-capable display will always be in Game mode. However, it
also has an "Off" mode, with which userspace can explicitly request that
VRR be disabled on the display sink.

The associated connector state member uses an u8 rather than the enum
type in order to fit into the existing 2 bytes of padding there. Once
the kernel mandates compiler versions that have backported C23 features,
the enum can be given an explicit small backing type and the member can
be changed to use the enum type.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_atomic_uapi.c |  4 ++
 drivers/gpu/drm/drm_connector.c   | 77 +++++++++++++++++++++++++++++++++++++++
 include/drm/drm_connector.h       | 29 +++++++++++++++
 3 files changed, 110 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 1eebcf6f5e05..127bdd77802b 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -969,6 +969,8 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 		state->hdmi.broadcast_rgb = val;
 	} else if (property == connector->color_format_property) {
 		state->color_format = val;
+	} else if (property == connector->vrr_display_mode_property) {
+		state->vrr_display_mode = val;
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1056,6 +1058,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
 		*val = state->hdmi.broadcast_rgb;
 	} else if (property == connector->color_format_property) {
 		*val = state->color_format;
+	} else if (property == connector->vrr_display_mode_property) {
+		*val = state->vrr_display_mode;
 	} else if (connector->funcs->atomic_get_property) {
 		return connector->funcs->atomic_get_property(connector,
 				state, property, val);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 893716a9e489..65c14b6e47a6 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -1434,6 +1434,12 @@ static const struct drm_prop_enum_list drm_tv_mode_enum_list[] = {
 };
 DRM_ENUM_NAME_FN(drm_get_tv_mode_name, drm_tv_mode_enum_list)
 
+static const struct drm_prop_enum_list drm_vrr_mode_enum_list[] = {
+	{ VRR_DISPLAY_MODE_GAME,   "Game" },
+	{ VRR_DISPLAY_MODE_OFF,    "Off" },
+};
+DRM_ENUM_NAME_FN(drm_get_vrr_mode_name, drm_vrr_mode_enum_list)
+
 /**
  * drm_get_tv_mode_from_name - Translates a TV mode name into its enum value
  * @name: TV Mode name we want to convert
@@ -2560,6 +2566,14 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *
  *	The driver may place further restrictions within these minimum
  *	and maximum bounds.
+ *
+ * "vrr_display_mode":
+ *	Optional &drm_connector enum property that controls which VRR mode, if
+ *	any, a display is in. Possible values::
+ *
+ *	    - Game - set display to game mode if display supports it,
+ *	      regardless of CRTC state
+ *	    - Off - set display to VRR disabled mode, regardless of CRTC state
  */
 
 /**
@@ -2593,6 +2607,69 @@ int drm_connector_attach_vrr_capable_property(
 }
 EXPORT_SYMBOL(drm_connector_attach_vrr_capable_property);
 
+/**
+ * drm_connector_attach_vrr_display_mode_property - creates and attaches VRR
+ * display mode property
+ * @connector: pointer to the &struct drm_connector to attach the property to
+ * @supported_vrr_modes: bitmask of VRR modes the connector supports regardless
+ *			 of sink
+ *
+ * Creates and attaches the "vrr_display_mode" property to @connector. The
+ * property must not already exist on it. @supported_vrr_modes is a bitmask
+ * wherein BIT(VRR_DISPLAY_MODE_FOO) indicates VRR_DISPLAY_MODE_FOO is supported
+ * by the connector.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int drm_connector_attach_vrr_display_mode_property(struct drm_connector *connector,
+						   unsigned int supported_vrr_modes)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_prop_enum_list enum_list[VRR_DISPLAY_MODE_COUNT];
+	unsigned int num_enums = 0;
+	struct drm_property *prop;
+	int i;
+
+	if (supported_vrr_modes & ~(BIT(VRR_DISPLAY_MODE_COUNT) - 1)) {
+		drm_err(dev, "Unknown VRR modes provided on [CONNECTOR:%d:%s]\n",
+			connector->base.id, connector->name);
+		return -EINVAL;
+	}
+
+	if (connector->vrr_display_mode_property) {
+		drm_err(dev, "[CONNECTOR:%d:%s] already has a 'vrr_display_mode' property\n",
+			connector->base.id, connector->name);
+		return -EEXIST;
+	}
+
+	if (!(supported_vrr_modes & BIT(VRR_DISPLAY_MODE_GAME))) {
+		drm_err(dev, "Missing default VRR mode on [CONNECTOR:%d:%s]\n",
+			connector->base.id, connector->name);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < VRR_DISPLAY_MODE_COUNT; i++) {
+		if (BIT(i) & supported_vrr_modes) {
+			enum_list[num_enums].name = drm_get_vrr_mode_name(i);
+			enum_list[num_enums].type = i;
+			num_enums++;
+		}
+	}
+
+	prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, "vrr_display_mode",
+					enum_list, num_enums);
+
+	if (!prop)
+		return -ENOMEM;
+
+	connector->vrr_display_mode_property = prop;
+	drm_object_attach_property(&connector->base, prop, VRR_DISPLAY_MODE_GAME);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_vrr_display_mode_property);
+
 /**
  * drm_connector_attach_scaling_mode_property - attach atomic scaling mode property
  * @connector: connector to attach scaling mode property on.
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 2ee7c59329aa..8699f35cafcb 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -809,6 +809,15 @@ enum drm_bus_flags {
 	DRM_BUS_FLAG_SHARP_SIGNALS = BIT(8),
 };
 
+enum vrr_display_mode {
+	/** @VRR_DISPLAY_MODE_GAME: Set the display to VRR "Game" mode */
+	VRR_DISPLAY_MODE_GAME = 0,
+	/** @VRR_DISPLAY_MODE_OFF: Turn off VRR on the display */
+	VRR_DISPLAY_MODE_OFF,
+	/** @VRR_DISPLAY_MODE_COUNT: Sentinel, not a valid mode, keep last */
+	VRR_DISPLAY_MODE_COUNT
+};
+
 /**
  * struct drm_amd_vsdb_info - AMD-specific VSDB information
  *
@@ -1352,6 +1361,12 @@ struct drm_connector_state {
 	 */
 	u8 max_bpc;
 
+	/**
+	 * @vrr_display_mode: One of &enum vrr_display_mode to indicate the VRR
+	 * type to use on a VRR-capable display.
+	 */
+	u8 vrr_display_mode;
+
 	/**
 	 * @privacy_screen_sw_state: See :ref:`Standard Connector
 	 * Properties<standard_connector_properties>`
@@ -2454,6 +2469,17 @@ struct drm_connector {
 	 */
 	struct drm_property *vrr_capable_property;
 
+	/**
+	 * @vrr_display_mode_property: Optional property for userspace to set
+	 * a connected display to a specific VRR mode, regardless of the
+	 * connected CRTC's VRR_ENABLED property. Drivers can create and attach
+	 * the property to a connector by calling
+	 * drm_connector_attach_vrr_display_mode_property(). Implementations can
+	 * then read the set display mode (&enum vrr_display_mode) from
+	 * &drm_connector_state.vrr_display_mode.
+	 */
+	struct drm_property *vrr_display_mode_property;
+
 	/**
 	 * @colorspace_property: Connector property to set the suitable
 	 * colorspace supported by the sink.
@@ -2859,6 +2885,7 @@ const char *drm_get_dpms_name(int val);
 const char *drm_get_dvi_i_subconnector_name(int val);
 const char *drm_get_dvi_i_select_name(int val);
 const char *drm_get_tv_mode_name(int val);
+const char *drm_get_vrr_mode_name(int val);
 const char *drm_get_tv_subconnector_name(int val);
 const char *drm_get_tv_select_name(int val);
 const char *drm_get_dp_subconnector_name(int val);
@@ -2883,6 +2910,8 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector,
 					       u32 scaling_mode_mask);
 int drm_connector_attach_vrr_capable_property(
 		struct drm_connector *connector);
+int drm_connector_attach_vrr_display_mode_property(struct drm_connector *connector,
+						   unsigned int supported_vrr_modes);
 void drm_connector_attach_panel_type_property(struct drm_connector *connector);
 int drm_connector_attach_broadcast_rgb_property(struct drm_connector *connector);
 int drm_connector_attach_colorspace_property(struct drm_connector *connector);

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 03/27] video/hdmi: Add VTEM EMP packing
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 01/27] drm: Add VRR state Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 02/27] drm/connector: add vrr_display_mode connector property Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 04/27] drm/bridge: Add VTEM EMP support Nicolas Frattaroli
                   ` (23 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

From: Derek Foreman <derek.foreman@collabora.com>

VTEM EMPs are a type of infoframe that is required for Variable Refresh
Rate. Add code to pack them.

Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/video/hdmi.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/hdmi.h | 19 ++++++++++
 2 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index 45b42f14a750..ed9abc7ad228 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -983,6 +983,10 @@ hdmi_infoframe_pack(union hdmi_infoframe *frame,
 		length = hdmi_vendor_any_infoframe_pack(&frame->vendor,
 							buffer, size);
 		break;
+	case HDMI_INFOFRAME_TYPE_EMP:
+		length = hdmi_emp_infoframe_vtem_pack(&frame->vtem,
+						      buffer, size);
+		break;
 	default:
 		WARN(1, "Bad infoframe type %d\n", frame->any.type);
 		length = -EINVAL;
@@ -994,9 +998,11 @@ EXPORT_SYMBOL(hdmi_infoframe_pack);
 
 static const char *hdmi_infoframe_type_get_name(enum hdmi_infoframe_type type)
 {
-	if (type < 0x80 || type > 0x9f)
+	if (type < 0x7F || type > 0x9f)
 		return "Invalid";
 	switch (type) {
+	case HDMI_INFOFRAME_TYPE_EMP:
+		return "EMP";
 	case HDMI_INFOFRAME_TYPE_VENDOR:
 		return "Vendor";
 	case HDMI_INFOFRAME_TYPE_AVI:
@@ -1516,6 +1522,29 @@ hdmi_vendor_any_infoframe_log(const char *level,
 	}
 }
 
+static void
+hdmi_emp_infoframe_vtem_log(const char *level,
+			    struct device *dev,
+			    const struct hdmi_emp_infoframe_vtem *frame)
+{
+	hdmi_infoframe_log_header(level, dev,
+				  (const struct hdmi_any_infoframe *)frame);
+
+	hdmi_log("    game vrr enabled: %u\n", frame->game_vrr_en);
+	hdmi_log("    fva factor minus 1: %u\n", frame->fva_factor_m1);
+	hdmi_log("    m_const: %u\n", frame->m_const);
+	hdmi_log("    base vertical front porch: %u\n", frame->base_vfront);
+	hdmi_log("    base refresh rate: %u\n", frame->base_refresh_rate);
+}
+
+static void
+hdmi_emp_infoframe_log(const char *level,
+		       struct device *dev,
+		       const union hdmi_infoframe *frame)
+{
+	/* Only support VTEM for now */
+	hdmi_emp_infoframe_vtem_log(level, dev, &frame->vtem);
+}
 /**
  * hdmi_infoframe_log() - log info of HDMI infoframe
  * @level: logging level
@@ -1542,6 +1571,9 @@ void hdmi_infoframe_log(const char *level,
 	case HDMI_INFOFRAME_TYPE_DRM:
 		hdmi_drm_infoframe_log(level, dev, &frame->drm);
 		break;
+	case HDMI_INFOFRAME_TYPE_EMP:
+		hdmi_emp_infoframe_log(level, dev, frame);
+		break;
 	}
 }
 EXPORT_SYMBOL(hdmi_infoframe_log);
@@ -1919,3 +1951,66 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame,
 	return ret;
 }
 EXPORT_SYMBOL(hdmi_infoframe_unpack);
+
+/**
+ * hdmi_emp_infoframe_vtem_init() - initialize an HDMI VTEM EMP
+ * @emp: HDMI VTEM EMP
+ */
+void hdmi_emp_infoframe_vtem_init(struct hdmi_emp_infoframe_vtem *emp)
+{
+	memset(emp, 0, sizeof(*emp));
+	emp->type = HDMI_INFOFRAME_TYPE_EMP;
+}
+EXPORT_SYMBOL(hdmi_emp_infoframe_vtem_init);
+
+/**
+ * hdmi_emp_infoframe_vtem_pack() - pack a vtem into a binary buffer
+ * @vtem: pointer to &struct hdmi_emp_infoframe_vtem to pack
+ * @buffer: pointer to pre-allocated output buffer
+ * @size: maximum size in bytes of @buffer
+ *
+ * Pack a VTEM infoframe struct into a binary buffer in a way that's suitable
+ * to be sent out over the wire.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_emp_infoframe_vtem_pack(struct hdmi_emp_infoframe_vtem *vtem,
+				     void *buffer, size_t size)
+{
+	u8 *ptr = buffer;
+
+	if (size < HDMI_INFOFRAME_SIZE(VTEM))
+		return -EINVAL;
+
+	/* 4 bits max */
+	if (vtem->fva_factor_m1 > 0xF)
+		return -EINVAL;
+
+	memset(buffer, 0, size);
+
+	/* Header Data */
+	ptr[0] = vtem->type;
+	ptr[1] = BIT(7) | BIT(6);
+	ptr[2] = 0;
+
+	ptr[3] = BIT(7) | BIT(2);
+
+	ptr[4] = 0;
+	ptr[5] = 1;
+	ptr[6] = 0;
+	ptr[7] = 1;
+	ptr[8] = 0;
+	ptr[9] = 4;
+
+	/* This is where the VTEM part starts */
+	ptr[10] = (vtem->fva_factor_m1 << 4) |
+		  (vtem->m_const ? BIT(1) : 0) |
+		  (vtem->game_vrr_en ? BIT(0) : 0);
+	ptr[11] = vtem->base_vfront;
+	ptr[12] = vtem->base_refresh_rate >> 8 & 0x3;
+	ptr[13] = vtem->base_refresh_rate & 0xFF;
+
+	return 14;
+}
+EXPORT_SYMBOL(hdmi_emp_infoframe_vtem_pack);
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index b80a5ee63bb2..71e2b36896af 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -55,6 +55,7 @@ enum hdmi_packet_type {
 };
 
 enum hdmi_infoframe_type {
+	HDMI_INFOFRAME_TYPE_EMP = 0x7F,
 	HDMI_INFOFRAME_TYPE_VENDOR = 0x81,
 	HDMI_INFOFRAME_TYPE_AVI = 0x82,
 	HDMI_INFOFRAME_TYPE_SPD = 0x83,
@@ -76,6 +77,7 @@ enum hdmi_infoframe_type {
 #define HDMI_AUDIO_INFOFRAME_SIZE  10
 #define HDMI_DRM_INFOFRAME_SIZE    26
 #define HDMI_VENDOR_INFOFRAME_SIZE  4
+#define HDMI_VTEM_INFOFRAME_SIZE   10
 
 /*
  * HDMI 1.3a table 5-14 states that the largest InfoFrame_length is 27,
@@ -235,6 +237,18 @@ struct hdmi_drm_infoframe {
 	u16 max_fall;
 };
 
+struct hdmi_emp_infoframe_vtem {
+	enum hdmi_infoframe_type type;
+	unsigned char version;
+	unsigned char length;
+
+	bool game_vrr_en;
+	bool m_const;
+	unsigned char fva_factor_m1;
+	unsigned char base_vfront;
+	u16 base_refresh_rate;
+};
+
 void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
 ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer,
 				size_t size);
@@ -249,6 +263,10 @@ ssize_t hdmi_drm_infoframe_pack_only(const struct hdmi_drm_infoframe *frame,
 int hdmi_drm_infoframe_check(struct hdmi_drm_infoframe *frame);
 int hdmi_drm_infoframe_unpack_only(struct hdmi_drm_infoframe *frame,
 				   const void *buffer, size_t size);
+void hdmi_emp_infoframe_vtem_init(struct hdmi_emp_infoframe_vtem *frame);
+ssize_t hdmi_emp_infoframe_vtem_pack(struct hdmi_emp_infoframe_vtem *frame,
+				     void *buffer,
+				     size_t size);
 
 enum hdmi_spd_sdi {
 	HDMI_SPD_SDI_UNKNOWN,
@@ -457,6 +475,7 @@ union hdmi_infoframe {
 	union hdmi_vendor_any_infoframe vendor;
 	struct hdmi_audio_infoframe audio;
 	struct hdmi_drm_infoframe drm;
+	struct hdmi_emp_infoframe_vtem vtem;
 };
 
 ssize_t hdmi_infoframe_pack(union hdmi_infoframe *frame, void *buffer,

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 04/27] drm/bridge: Add VTEM EMP support
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (2 preceding siblings ...)
  2026-11-05 15:51 ` [PATCH v2 03/27] video/hdmi: Add VTEM EMP packing Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 05/27] drm/connector: hdmi: Add VTEM EMP generation Nicolas Frattaroli
                   ` (22 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

From: Derek Foreman <derek.foreman@collabora.com>

Add setters and support bit for VTEM emp to the bridge.

Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 41 ++++++++++++++++++++++++++
 include/drm/drm_bridge.h                       | 29 ++++++++++++++++++
 include/drm/drm_connector.h                    |  8 +++++
 3 files changed, 78 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 2202e6044444..0fbdba584c1b 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -611,6 +611,33 @@ static int drm_bridge_connector_scrambler_disable(struct drm_connector *connecto
 	return bridge->funcs->hdmi_scrambler_disable(bridge);
 }
 
+static int drm_bridge_connector_clear_vtem_emp(struct drm_connector *connector)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *bridge;
+
+	bridge = bridge_connector->bridge_hdmi;
+	if (!bridge)
+		return -EINVAL;
+
+	return bridge->funcs->hdmi_clear_vtem_emp(bridge);
+}
+
+static int drm_bridge_connector_write_vtem_emp(struct drm_connector *connector,
+					       const u8 *buffer, size_t len)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *bridge;
+
+	bridge = bridge_connector->bridge_hdmi;
+	if (!bridge)
+		return -EINVAL;
+
+	return bridge->funcs->hdmi_write_vtem_emp(bridge, buffer, len);
+}
+
 static const struct drm_edid *
 drm_bridge_connector_read_edid(struct drm_connector *connector)
 {
@@ -656,6 +683,11 @@ static const struct drm_connector_infoframe_funcs drm_bridge_connector_hdmi_spd_
 	.write_infoframe = drm_bridge_connector_write_spd_infoframe,
 };
 
+static const struct drm_connector_infoframe_funcs drm_bridge_connector_hdmi_vtem_emp = {
+	.clear_infoframe = drm_bridge_connector_clear_vtem_emp,
+	.write_infoframe = drm_bridge_connector_write_vtem_emp,
+};
+
 static int drm_bridge_connector_audio_startup(struct drm_connector *connector)
 {
 	struct drm_bridge_connector *bridge_connector =
@@ -947,6 +979,11 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 			     !bridge->funcs->hdmi_scrambler_disable))
 				return ERR_PTR(-EINVAL);
 
+			if (bridge->ops & DRM_BRIDGE_OP_HDMI_VTEM_EMP &&
+			    (!bridge->funcs->hdmi_write_vtem_emp ||
+			     !bridge->funcs->hdmi_clear_vtem_emp))
+				return ERR_PTR(-EINVAL);
+
 			bridge_connector->bridge_hdmi = drm_bridge_get(bridge);
 		}
 
@@ -1073,6 +1110,10 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 				drm_bridge_connector_scrambler_disable;
 		}
 
+		if (bridge_connector->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_VTEM_EMP)
+			bridge_connector->hdmi_funcs.vtem =
+				drm_bridge_connector_hdmi_vtem_emp;
+
 		ret = drmm_connector_hdmi_init(drm, connector,
 					       &drm_bridge_connector_funcs,
 					       &bridge_connector->hdmi_funcs,
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 1981d24a700d..6913662023ca 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -684,6 +684,29 @@ struct drm_bridge_funcs {
 	int (*hdmi_write_spd_infoframe)(struct drm_bridge *bridge,
 					const u8 *buffer, size_t len);
 
+	/**
+	 * @hdmi_clear_vtem_emp:
+	 *
+	 * This callback clears the emp in the hardware during commit.
+	 *
+	 * This callback is optional but it must be implemented by bridges that
+	 * set the DRM_BRIDGE_OP_HDMI_VTEM_EMP flag in their
+	 * &drm_bridge->ops.
+	 */
+	int (*hdmi_clear_vtem_emp)(struct drm_bridge *bridge);
+
+	/**
+	 * @hdmi_write_spd_infoframe:
+	 *
+	 * Program the emp into the hardware.
+	 *
+	 * This callback is optional but it must be implemented by bridges that
+	 * set the DRM_BRIDGE_OP_HDMI_VTEM_EMP flag in their
+	 * &drm_bridge->ops.
+	 */
+	int (*hdmi_write_vtem_emp)(struct drm_bridge *bridge,
+				   const u8 *buffer, size_t len);
+
 	/**
 	 * @hdmi_clear_audio_infoframe:
 	 *
@@ -1021,6 +1044,12 @@ enum drm_bridge_ops {
 	 * &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks.
 	 */
 	DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10),
+	/**
+	 * @DRM_BRIDGE_OP_HDMI_VTEM_EMP: The bridge supports
+	 * &drm_bridge_funcs->hdmi_write_vtem_emp and
+	 * &drm_bridge_funcs->hdmi_clear_vtem_emp callbacks.
+	 */
+	DRM_BRIDGE_OP_HDMI_VTEM_EMP = BIT(11),
 };
 
 /**
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 8699f35cafcb..184b10c89508 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1645,6 +1645,14 @@ struct drm_connector_hdmi_funcs {
 	 * optional.
 	 */
 	struct drm_connector_infoframe_funcs spd;
+
+	/**
+	 * @vtem:
+	 *
+	 * Set of callbacks for handling the VTEM EMP. These callbacks are
+	 * optional.
+	 */
+	struct drm_connector_infoframe_funcs vtem;
 };
 
 /**

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 05/27] drm/connector: hdmi: Add VTEM EMP generation
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (3 preceding siblings ...)
  2026-11-05 15:51 ` [PATCH v2 04/27] drm/bridge: Add VTEM EMP support Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 06/27] drm/crtc-helper: Add VRR helper functions Nicolas Frattaroli
                   ` (21 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

From: Derek Foreman <derek.foreman@collabora.com>

Add VTEM EMP generation to enable variable refresh rate signalling over
HDMI.

These infoframes are only generated if the sink supports VRR.

Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 63 +++++++++++++++++++++++++
 include/drm/drm_connector.h                     |  5 ++
 2 files changed, 68 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index eb6abfd1350d..ce680eaa5047 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -853,6 +853,61 @@ static int hdmi_generate_hdmi_vendor_infoframe(const struct drm_connector *conne
 	return 0;
 }
 
+static int hdmi_generate_emp_infoframe_vtem(const struct drm_connector *connector,
+					    struct drm_connector_state *conn_state)
+{
+	const struct drm_display_info *info = &connector->display_info;
+	const struct drm_crtc_state *crtc_state =
+		drm_atomic_get_crtc_state(conn_state->state, conn_state->crtc);
+	struct drm_connector_hdmi_infoframe *infoframe =
+		&conn_state->hdmi.infoframes.vtem;
+	struct hdmi_emp_infoframe_vtem *vtem =
+		&infoframe->data.vtem;
+	const struct drm_crtc_vrr_state *vrr = &crtc_state->vrr_state;
+	int vfront;
+	u8 avi_vic;
+
+	infoframe->set = false;
+
+	if (!connector->hdmi.funcs->vtem.write_infoframe)
+		return 0;
+
+	if (!info->hdmi.vrr_cap.supported)
+		return 0;
+
+	if (conn_state->hdmi.infoframes.avi.set)
+		avi_vic = conn_state->hdmi.infoframes.avi.data.avi.video_code;
+	else
+		avi_vic = 0;
+
+	hdmi_emp_infoframe_vtem_init(vtem);
+
+	switch (conn_state->vrr_display_mode) {
+	case VRR_DISPLAY_MODE_GAME:
+		vtem->game_vrr_en = true;
+		vtem->m_const = !vrr->dynamic;
+		break;
+	case VRR_DISPLAY_MODE_OFF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (vtem->game_vrr_en && !avi_vic) {
+		vtem->base_refresh_rate = drm_mode_vrefresh(&crtc_state->mode);
+		vfront = crtc_state->adjusted_mode.crtc_vsync_start -
+			 crtc_state->adjusted_mode.crtc_vdisplay;
+		if (vfront > U8_MAX || vfront < 0)
+			return -EINVAL;
+
+		vtem->base_vfront = vfront;
+	}
+
+	infoframe->set = true;
+
+	return 0;
+}
+
 static int
 hdmi_generate_infoframes(const struct drm_connector *connector,
 			 struct drm_connector_state *conn_state)
@@ -884,6 +939,10 @@ hdmi_generate_infoframes(const struct drm_connector *connector,
 	if (ret)
 		return ret;
 
+	ret = hdmi_generate_emp_infoframe_vtem(connector, conn_state);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
@@ -1475,6 +1534,10 @@ int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *con
 			goto out;
 	}
 
+	ret = write_or_clear_infoframe(connector, &funcs->vtem, "VTEM",
+				       &old_conn_state->hdmi.infoframes.vtem,
+				       &new_conn_state->hdmi.infoframes.vtem);
+
 out:
 	mutex_unlock(&connector->hdmi.infoframes.lock);
 	return ret;
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 184b10c89508..9df745ecff92 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1186,6 +1186,11 @@ struct drm_connector_hdmi_state {
 		 * matching our state.
 		 */
 		struct drm_connector_hdmi_infoframe hdmi;
+
+		/**
+		 * @vtem: VTEM EMP infoframes structure matching our state.
+		 */
+		struct drm_connector_hdmi_infoframe vtem;
 	} infoframes;
 
 	/**

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 06/27] drm/crtc-helper: Add VRR helper functions
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (4 preceding siblings ...)
  2026-11-05 15:51 ` [PATCH v2 05/27] drm/connector: hdmi: Add VTEM EMP generation Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 07/27] drm/bridge: synopsys: Add VTEM EMP support Nicolas Frattaroli
                   ` (20 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

VRR needs to change the vertical front porch timing to change the duration
of a frame. Add two helpers to aid in this.

drm_crtc_helper_vrr_flush should be called when the source has a new
frame ready to present. It adjusts the internal VRR state, and returns
whether the current vtotal has been changed. Callers can then adjust
their hardware state as is necessary.

drm_crtc_helper_vrr_vsync should be called in the vblank interrupt
handler. It adjusts the internal VRR state, and returns whether the
current vtotal has been changed. Callers can then adjust their hardware
state as is necessary.

Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_crtc_helper.c | 63 +++++++++++++++++++++++++++++++++++++++
 include/drm/drm_crtc_helper.h     |  4 +++
 2 files changed, 67 insertions(+)

diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
index 4a8cfe046e4a..4ffb3b0b7878 100644
--- a/drivers/gpu/drm/drm_crtc_helper.c
+++ b/drivers/gpu/drm/drm_crtc_helper.c
@@ -838,6 +838,69 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set,
 }
 EXPORT_SYMBOL(drm_crtc_helper_set_config);
 
+/**
+ * drm_crtc_helper_vrr_flush - Update VRR state on frame ready
+ * @crtc_state: pointer to &struct drm_crtc_state of CRTC that has a new frame ready
+ *
+ * Update the VRR state by communicating that a frame is ready on
+ * @crtc_state.crtc.
+ *
+ * Should be called from the @crtc_state.crtc &drm_crtc_helper_funcs.atomic_flush
+ * hook.
+ *
+ * Returns:
+ * - %true if @crtc_state.vrr_state.cur_vtotal changed
+ * - %false otherwise
+ */
+bool drm_crtc_helper_vrr_flush(struct drm_crtc_state *crtc_state)
+{
+	struct drm_crtc_vrr_state *vrr = &crtc_state->vrr_state;
+
+	if (!crtc_state->vrr_enabled) {
+		if (vrr->cur_vtotal) {
+			vrr->cur_vtotal = 0;
+			return true;
+		}
+		return false;
+	}
+
+	if (vrr->cur_vtotal == vrr->base_vtotal)
+		return false;
+
+	vrr->cur_vtotal = vrr->base_vtotal;
+
+	return true;
+}
+EXPORT_SYMBOL(drm_crtc_helper_vrr_flush);
+
+/**
+ * drm_crtc_helper_vrr_vsync - Update VRR state on vblank end
+ * @crtc_state: pointer to &struct drm_crtc_state of CRTC the vblank occurred on
+ *
+ * Update the VRR state by communicating that a vertical blank has ended and a
+ * new frame is about to start.
+ *
+ * Returns:
+ *  - %true if @crtc_state.vrr_state.cur_vtotal changed
+ *  - %false otherwise
+ */
+bool drm_crtc_helper_vrr_vsync(struct drm_crtc_state *crtc_state)
+{
+	struct drm_crtc_vrr_state *vrr = &crtc_state->vrr_state;
+	u16 old_vtotal = vrr->cur_vtotal;
+
+	if (!crtc_state->vrr_enabled)
+		vrr->cur_vtotal = 0;
+	else
+		vrr->cur_vtotal = vrr->max_vtotal;
+
+	if (abs_diff(vrr->cur_vtotal, old_vtotal) > 1)
+		vrr->dynamic = true;
+
+	return old_vtotal != vrr->cur_vtotal;
+}
+EXPORT_SYMBOL(drm_crtc_helper_vrr_vsync);
+
 static int drm_helper_choose_encoder_dpms(struct drm_encoder *encoder)
 {
 	int dpms = DRM_MODE_DPMS_OFF;
diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h
index 855da5733c47..0c8c11ea36dd 100644
--- a/include/drm/drm_crtc_helper.h
+++ b/include/drm/drm_crtc_helper.h
@@ -44,6 +44,7 @@ struct drm_encoder;
 struct drm_framebuffer;
 struct drm_mode_set;
 struct drm_modeset_acquire_ctx;
+struct drm_crtc_state;
 
 void drm_helper_disable_unused_functions(struct drm_device *dev);
 int drm_crtc_helper_set_config(struct drm_mode_set *set,
@@ -62,4 +63,7 @@ int drm_helper_connector_dpms(struct drm_connector *connector, int mode);
 void drm_helper_resume_force_mode(struct drm_device *dev);
 int drm_helper_force_disable_all(struct drm_device *dev);
 
+bool drm_crtc_helper_vrr_flush(struct drm_crtc_state *crtc_state);
+bool drm_crtc_helper_vrr_vsync(struct drm_crtc_state *crtc_state);
+
 #endif

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 07/27] drm/bridge: synopsys: Add VTEM EMP support
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (5 preceding siblings ...)
  2026-11-05 15:51 ` [PATCH v2 06/27] drm/crtc-helper: Add VRR helper functions Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:51 ` [PATCH v2 08/27] drm/connector: Add drm_display_info_is_vrr_capable Nicolas Frattaroli
                   ` (19 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

From: Derek Foreman <derek.foreman@collabora.com>

Add code to pack and schedule VTEM EMPs.

Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
---
 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 50 +++++++++++++++++++++++++++-
 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h |  1 +
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
index b268c9376087..8b3f586dc177 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
@@ -689,6 +689,15 @@ static int dw_hdmi_qp_bridge_clear_spd_infoframe(struct drm_bridge *bridge)
 	return 0;
 }
 
+static int dw_hdmi_qp_bridge_clear_vtem_emp(struct drm_bridge *bridge)
+{
+	struct dw_hdmi_qp *hdmi = bridge->driver_private;
+
+	dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_EMP_VTEM_TX_EN, PKTSCHED_PKT_EN);
+
+	return 0;
+}
+
 static int dw_hdmi_qp_bridge_clear_audio_infoframe(struct drm_bridge *bridge)
 {
 	struct dw_hdmi_qp *hdmi = bridge->driver_private;
@@ -791,6 +800,42 @@ static int dw_hdmi_qp_bridge_write_spd_infoframe(struct drm_bridge *bridge,
 	return 0;
 }
 
+static int dw_hdmi_qp_bridge_write_vtem_emp(struct drm_bridge *bridge,
+					    const u8 *buffer, size_t len)
+{
+	struct dw_hdmi_qp *hdmi = bridge->driver_private;
+	u32 val, i;
+
+	if (len != HDMI_INFOFRAME_SIZE(VTEM)) {
+		dev_err(hdmi->dev, "unsupported vtem emp packet size of %zu\n", len);
+		return -EINVAL;
+	}
+
+	val = buffer[1] << 8 | buffer[2] << 16;
+	dw_hdmi_qp_write(hdmi, val, PKT_EMP_VTEM_CONTENTS0);
+
+	val = buffer[3] | buffer[4] << 8;
+	dw_hdmi_qp_write(hdmi, val, PKT_EMP_VTEM_CONTENTS1);
+
+	val = buffer[8] << 8 | buffer[9] << 16 | buffer[10] << 24;
+	dw_hdmi_qp_write(hdmi, val, PKT_EMP_VTEM_CONTENTS2);
+
+	val = buffer[11] | buffer[12] << 8 | buffer[13] << 16;
+	dw_hdmi_qp_write(hdmi, val, PKT_EMP_VTEM_CONTENTS3);
+
+	/*
+	 * The remaining registers need to explicitly be rewritten even if they
+	 * stay at 0, as per the hardware documentation.
+	 */
+	for (i = PKT_EMP_VTEM_CONTENTS4; i <= PKT_EMP_VTEM_CONTENTS7; i += 4)
+		dw_hdmi_qp_write(hdmi, 0, i);
+
+	dw_hdmi_qp_mod(hdmi, PKTSCHED_EMP_VTEM_TX_EN, PKTSCHED_EMP_VTEM_TX_EN,
+			PKTSCHED_PKT_EN);
+
+	return 0;
+}
+
 static int dw_hdmi_qp_bridge_write_audio_infoframe(struct drm_bridge *bridge,
 						   const u8 *buffer, size_t len)
 {
@@ -1035,6 +1080,8 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
 	.hdmi_write_hdr_drm_infoframe = dw_hdmi_qp_bridge_write_hdr_drm_infoframe,
 	.hdmi_clear_spd_infoframe = dw_hdmi_qp_bridge_clear_spd_infoframe,
 	.hdmi_write_spd_infoframe = dw_hdmi_qp_bridge_write_spd_infoframe,
+	.hdmi_clear_vtem_emp = dw_hdmi_qp_bridge_clear_vtem_emp,
+	.hdmi_write_vtem_emp = dw_hdmi_qp_bridge_write_vtem_emp,
 	.hdmi_clear_audio_infoframe = dw_hdmi_qp_bridge_clear_audio_infoframe,
 	.hdmi_write_audio_infoframe = dw_hdmi_qp_bridge_write_audio_infoframe,
 	.hdmi_audio_startup = dw_hdmi_qp_audio_enable,
@@ -1154,7 +1201,8 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
 			   DRM_BRIDGE_OP_HDMI |
 			   DRM_BRIDGE_OP_HDMI_AUDIO |
 			   DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME |
-			   DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME;
+			   DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME |
+			   DRM_BRIDGE_OP_HDMI_VTEM_EMP;
 	hdmi->bridge.of_node = pdev->dev.of_node;
 	hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
 	hdmi->bridge.vendor = "Synopsys";
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
index c07847e8d7dd..6ce87064a267 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
@@ -210,6 +210,7 @@
 #define PKTSCHED_AVI_TX_EN				BIT(13)
 #define PKTSCHED_VSI_TX_EN				BIT(12)
 #define PKTSCHED_EMP_CVTEM_TX_EN			BIT(10)
+#define PKTSCHED_EMP_VTEM_TX_EN				BIT(9)
 #define PKTSCHED_AMD_TX_EN				BIT(8)
 #define PKTSCHED_GCP_TX_EN				BIT(3)
 #define PKTSCHED_AUDS_TX_EN				BIT(2)

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 08/27] drm/connector: Add drm_display_info_is_vrr_capable
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (6 preceding siblings ...)
  2026-11-05 15:51 ` [PATCH v2 07/27] drm/bridge: synopsys: Add VTEM EMP support Nicolas Frattaroli
@ 2026-11-05 15:51 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 09/27] drm: Add VRR target frame rate properties Nicolas Frattaroli
                   ` (18 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:51 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add a function to return whether a sink, as described by a
drm_display_info struct, is capable of receiving VRR signals.

For HDMI sinks, this can just check the HDMI specific vrr_cap.supported
field, which is set by the EDID parsing code. For other types of
displays, the generally agreed-upon method appears to be to check
whether the monitor range's minimum frequency is above 0.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_connector.c | 19 +++++++++++++++++++
 include/drm/drm_connector.h     |  2 ++
 2 files changed, 21 insertions(+)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 65c14b6e47a6..a44afba8c5ff 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -1378,6 +1378,25 @@ int drm_display_info_set_bus_formats(struct drm_display_info *info,
 }
 EXPORT_SYMBOL(drm_display_info_set_bus_formats);
 
+/**
+ * drm_display_info_is_vrr_capable - Check whether sink is VRR capable
+ * @info: pointer to a &struct drm_display_info representing the sink
+ *
+ * Check whether the sink supports variable refresh rate signals.
+ *
+ * Returns:
+ * - true if variable refresh rate is supported by the sink
+ * - false otherwise
+ */
+bool drm_display_info_is_vrr_capable(const struct drm_display_info *info)
+{
+	if (info->is_hdmi)
+		return info->hdmi.vrr_cap.supported;
+
+	return info->monitor_range.min_vfreq > 0;
+}
+EXPORT_SYMBOL(drm_display_info_is_vrr_capable);
+
 /* Optional connector properties. */
 static const struct drm_prop_enum_list drm_scaling_mode_enum_list[] = {
 	{ DRM_MODE_SCALE_NONE, "None" },
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 9df745ecff92..5b6920c1a996 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1078,6 +1078,8 @@ int drm_display_info_set_bus_formats(struct drm_display_info *info,
 				     const u32 *formats,
 				     unsigned int num_formats);
 
+bool drm_display_info_is_vrr_capable(const struct drm_display_info *info);
+
 /**
  * struct drm_connector_tv_margins - TV connector related margins
  *

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 09/27] drm: Add VRR target frame rate properties
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (7 preceding siblings ...)
  2026-11-05 15:51 ` [PATCH v2 08/27] drm/connector: Add drm_display_info_is_vrr_capable Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 10/27] drm/connector: Add vrr_display_{min,max} connector properties Nicolas Frattaroli
                   ` (17 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Userspace may wish to set a target frame rate for the Variable Refresh
Rate mechanism. There are multiple possible ways such a target could be
communicated.

It's desirable for userspace to not only set a fixed target, but a
target range within which VRR may operate. This is because even if a
sink advertises a certain range as supported, said range may not be the
ideal range to use, as numbers on the extreme end of the range may
induce flickering or other glitches.

There's four choices to how one bracketing value for the target range
could be communicated:
1. Target frame rate in something like millihertz
2. Target frame period in something like usecs
3. Target vtotal value
4. Target frame rate as a fraction expressed by two integers

Choice 1 is unpleasant as it would introduce some amount of rounding to
common broadcast framerates like 24/1.001, which seems minor except that
exact matches to standard framerates would rely on making all userspace
and kernel agree on a specific method of rounding. This is sure to go
wrong, so is disqualified.

Choice 2 has the same problem as 1, with the added complication that
the EDID's VRR range is expressed in frames per second, and other HDMI
features express things in frames per second as well, which would
require possibly imprecise computations.

Choice 3 is disqualified as mechanisms other than VRR, such as FVA, may
also modify the vtotal value. Additionally, it has the same drawbacks of
choice 2 and 1.

Choice 4 is therefore what's implemented. The frame rate 24/1.001Hz, for
example, is expressed as nominator=24000 and denominator=1001. As there
is both a minimum and a maximum, there are 4 properties in total: two
numerators, and two denominators. A fixed rate with no variability is
expressed by setting the minimum fractional and the maximum fractional
properties to the same value.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_atomic.c      | 14 ++++++++++++
 drivers/gpu/drm/drm_atomic_uapi.c | 24 ++++++++++++++++++++
 drivers/gpu/drm/drm_connector.c   | 48 +++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_crtc.c        | 34 +++++++++++++++++++++++++++
 drivers/gpu/drm/drm_mode_config.c | 20 ++++++++++++++++
 include/drm/drm_crtc.h            |  6 +++++
 include/drm/drm_mode_config.h     | 45 ++++++++++++++++++++++++++++++++++++
 7 files changed, 191 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 9bb761eda59f..da5c6925c349 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -489,6 +489,7 @@ EXPORT_SYMBOL(drm_atomic_get_crtc_state);
 static int drm_atomic_crtc_check(const struct drm_crtc_state *old_crtc_state,
 				 const struct drm_crtc_state *new_crtc_state)
 {
+	const struct drm_crtc_vrr_state *vrr = &new_crtc_state->vrr_state;
 	struct drm_crtc *crtc = new_crtc_state->crtc;
 
 	/* NOTE: we explicitly don't enforce constraints such as primary
@@ -544,6 +545,19 @@ static int drm_atomic_crtc_check(const struct drm_crtc_state *old_crtc_state,
 		return -EINVAL;
 	}
 
+	/*
+	 * Reject VRR limiter ranges that only specify one end of the limit, or
+	 * specify a numerator > 0 with a denominator that is 0.
+	 */
+	if ((!vrr->vrr_min_n || !vrr->vrr_min_d ||
+	     !vrr->vrr_max_n || !vrr->vrr_max_d) &&
+	    (vrr->vrr_min_n > 0 || vrr->vrr_max_n > 0)) {
+		drm_dbg_atomic(crtc->dev,
+			       "[CRTC:%d:%s] incomplete VRR limiter range\n",
+			       crtc->base.id, crtc->name);
+		return -EINVAL;
+	}
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 127bdd77802b..c95eaa9697c4 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -420,6 +420,22 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
 		return ret;
 	} else if (property == config->prop_vrr_enabled) {
 		state->vrr_enabled = val;
+	} else if (property == config->prop_vrr_min_numerator) {
+		if (val > U32_MAX)
+			return -EINVAL;
+		state->vrr_state.vrr_min_n = val;
+	} else if (property == config->prop_vrr_min_denominator) {
+		if (val > U32_MAX)
+			return -EINVAL;
+		state->vrr_state.vrr_min_d = val;
+	} else if (property == config->prop_vrr_max_numerator) {
+		if (val > U32_MAX)
+			return -EINVAL;
+		state->vrr_state.vrr_max_n = val;
+	} else if (property == config->prop_vrr_max_denominator) {
+		if (val > U32_MAX)
+			return -EINVAL;
+		state->vrr_state.vrr_max_d = val;
 	} else if (property == config->degamma_lut_property) {
 		const size_t elem_size = sizeof(struct drm_color_lut);
 		u64 lut_size;
@@ -505,6 +521,14 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
 		*val = (state->mode_blob) ? state->mode_blob->base.id : 0;
 	else if (property == config->prop_vrr_enabled)
 		*val = state->vrr_enabled;
+	else if (property == config->prop_vrr_min_numerator)
+		*val = state->vrr_state.vrr_min_n;
+	else if (property == config->prop_vrr_min_denominator)
+		*val = state->vrr_state.vrr_min_d;
+	else if (property == config->prop_vrr_max_numerator)
+		*val = state->vrr_state.vrr_max_n;
+	else if (property == config->prop_vrr_max_denominator)
+		*val = state->vrr_state.vrr_max_d;
 	else if (property == config->degamma_lut_property)
 		*val = (state->degamma_lut) ? state->degamma_lut->base.id : 0;
 	else if (property == config->ctm_property)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index a44afba8c5ff..33c4195db58b 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -2593,6 +2593,54 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *	    - Game - set display to game mode if display supports it,
  *	      regardless of CRTC state
  *	    - Off - set display to VRR disabled mode, regardless of CRTC state
+ *
+ * VRR Limiter/Target Properties
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * The ``VRR_{MIN,MAX}_{NUMERATOR,DENOMINATOR}`` properties expose a mechanism
+ * through which userspace can control the desired range of refresh rates in
+ * which VRR is allowed to operate. Each rate is expressed as a
+ * numerator/denominator fraction of refresh rates in Hz, allowing for rational
+ * target rates like 24/1.001 Hz with no loss of precision or ambiguity.
+ *
+ * If the minimum and maximum rate are set to the same value (and not 0), they
+ * are understood as a fixed target rate. This is especially useful for media
+ * playback, where the content's frame rate is both constant and known in
+ * advance. In such cases, a refresh rate that is not an integer multiple of the
+ * content's frame rate will introduce judder, since not every frame is
+ * displayed for the same amount of time. A modeset of the display with a
+ * compatible rate may in those cases be either undesirable or impossible, but
+ * the rate can still effectively be reached through VRR.
+ *
+ * If one end of the limit is set (e.g. minimum), the other end (e.g. maximum)
+ * must also be set. Any rational with a numerator of 0 is treated as 0/0
+ * regardless of denominator.
+ *
+ * .. _VRR-MIN-NUMERATOR:
+ *
+ * "VRR_MIN_NUMERATOR":
+ *	Default &drm_crtc integer property forming the numerator of a
+ *	numerator/denominator pair of a frame rate to set as the minimum VRR
+ *	target rate. Set to 0 to disable.
+ *
+ * "VRR_MIN_DENOMINATOR":
+ *	Default &drm_crtc integer property forming the denominator of a
+ *	numerator/denominator pair of a frame rate to set as the minimum VRR
+ *	target rate. If :ref:`VRR_MIN_NUMERATOR <VRR-MIN-NUMERATOR>` is not
+ *	zero, it must be non-zero.
+ *
+ * .. _VRR-MAX-NUMERATOR:
+ *
+ * "VRR_MAX_NUMERATOR":
+ *	Default &drm_crtc integer property forming the numerator of a
+ *	numerator/denominator pair of a frame rate to set as the maximum VRR
+ *	target rate. Set to 0 to disable.
+ *
+ * "VRR_MAX_DENOMINATOR":
+ *	Default &drm_crtc integer property forming the denominator of a
+ *	numerator/denominator pair of a frame rate to set as the maximum VRR
+ *	target rate. If :ref:`VRR_MAX_NUMERATOR <VRR-MAX-NUMERATOR>` is not
+ *	zero, it must be non-zero.
  */
 
 /**
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 5295ec122e0d..4c29c6e15df9 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -324,6 +324,40 @@ static int __drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *
 	return 0;
 }
 
+/**
+ * drm_crtc_attach_vrr_limiter_properties - Attach VRR limiter props to CRTC
+ * @crtc: The CRTC to attach the properties to
+ *
+ * Attach the VRR limiter target minimum and maximum frame rate properties to
+ * @crtc. The driver implementing @crtc must use atomic mode setting.
+ *
+ * This convenience function is to be used by drivers which implement all the
+ * logic required for the VRR limiter uAPI to function.
+ *
+ * Returns:
+ * Zero on success, negative error on failure.
+ */
+int drm_crtc_attach_vrr_limiter_properties(struct drm_crtc *crtc)
+{
+	struct drm_mode_config *config = &crtc->dev->mode_config;
+
+	if (!drm_drv_uses_atomic_modeset(crtc->dev))
+		return -EINVAL;
+
+	drm_object_attach_property(&crtc->base,
+				   config->prop_vrr_min_numerator, 0);
+	drm_object_attach_property(&crtc->base,
+				   config->prop_vrr_min_denominator, 0);
+	drm_object_attach_property(&crtc->base,
+				   config->prop_vrr_max_numerator, 0);
+	drm_object_attach_property(&crtc->base,
+				   config->prop_vrr_max_denominator, 0);
+
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_attach_vrr_limiter_properties);
+
 /**
  * drm_crtc_init_with_planes - Initialise a new CRTC object with
  *    specified primary and cursor planes.
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 48a56f23dadb..da967d60f832 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -535,6 +535,26 @@ static int drm_mode_create_standard_properties(struct drm_device *dev)
 		return -ENOMEM;
 	dev->mode_config.prop_vrr_enabled = prop;
 
+	prop = drm_property_create_range(dev, 0, "VRR_MIN_NUMERATOR", 0, U32_MAX);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.prop_vrr_min_numerator = prop;
+
+	prop = drm_property_create_range(dev, 0, "VRR_MIN_DENOMINATOR", 0, U32_MAX);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.prop_vrr_min_denominator = prop;
+
+	prop = drm_property_create_range(dev, 0, "VRR_MAX_NUMERATOR", 0, U32_MAX);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.prop_vrr_max_numerator = prop;
+
+	prop = drm_property_create_range(dev, 0, "VRR_MAX_DENOMINATOR", 0, U32_MAX);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.prop_vrr_max_denominator = prop;
+
 	prop = drm_property_create(dev,
 			DRM_MODE_PROP_BLOB,
 			"DEGAMMA_LUT", 0);
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index bfb2b0741625..9004f7c2f21e 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -66,6 +66,10 @@ struct drm_plane_helper_funcs;
 struct drm_crtc_vrr_state {
 	u16 cur_vtotal;
 	u16 max_vtotal;
+	u32 vrr_min_n;
+	u32 vrr_min_d;
+	u32 vrr_max_n;
+	u32 vrr_max_d;
 	u16 base_vtotal;
 	bool dynamic;
 };
@@ -1247,6 +1251,8 @@ struct drm_mode_set {
 
 #define obj_to_crtc(x) container_of(x, struct drm_crtc, base)
 
+int drm_crtc_attach_vrr_limiter_properties(struct drm_crtc *crtc);
+
 __printf(6, 7)
 int drm_crtc_init_with_planes(struct drm_device *dev,
 			      struct drm_crtc *crtc,
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 084517beb924..c8d5286f9267 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -702,6 +702,51 @@ struct drm_mode_config {
 	 */
 	struct drm_property *prop_vrr_enabled;
 
+	/**
+	 * @prop_vrr_min_numerator: Default atomic CRTC property to indicate the
+	 * numerator of a numerator/denominator variable refresh rate minimum
+	 * target frame rate.
+	 *
+	 * If not zero, @prop_vrr_min_numerator divided by
+	 * @prop_vrr_min_denominator must result in a frame rate above the
+	 * sink's minimum VRR rate.
+	 */
+	struct drm_property *prop_vrr_min_numerator;
+
+	/**
+	 * @prop_vrr_min_denominator: Default atomic CRTC property to indicate
+	 * the denominator of a numerator/denominator variable refresh rate
+	 * minimum target frame rate.
+	 *
+	 * If @prop_vrr_min_numerator is set, @prop_vrr_min_denominator must not
+	 * be 0, and @prop_vrr_min_numerator divided by @prop_vrr_min_denominator
+	 * must result in a frame rate above the minimum VRR rate.
+	 */
+	struct drm_property *prop_vrr_min_denominator;
+
+	/**
+	 * @prop_vrr_max_numerator: Default atomic CRTC property to indicate the
+	 * numerator of a numerator/denominator variable refresh rate maximum
+	 * target frame rate range.
+	 *
+	 * The fraction expressed by this property divided by its corresponding
+	 * denominator must be equal to or greater than the value of
+	 * @prop_vrr_min_numerator.
+	 */
+	struct drm_property *prop_vrr_max_numerator;
+
+	/**
+	 * @prop_vrr_max_denominator: Default atomic CRTC property to indicate
+	 * the denominator of a numerator/denominator variable refresh rate
+	 * maximum target frame rate range.
+	 *
+	 * If @prop_vrr_max_numerator is set, @prop_vrr_max_denominator must not
+	 * be 0, and @prop_vrr_max_numerator divided by @prop_vrr_max_denominator
+	 * must result in a frame rate equal to or above @prop_vrr_min_numerator
+	 * divided by @prop_vrr_min_denominator.
+	 */
+	struct drm_property *prop_vrr_max_denominator;
+
 	/**
 	 * @dvi_i_subconnector_property: Optional DVI-I property to
 	 * differentiate between analog or digital mode.

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 10/27] drm/connector: Add vrr_display_{min,max} connector properties
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (8 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 09/27] drm: Add VRR target frame rate properties Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 11/27] drm: Implement VRR rate limiting Nicolas Frattaroli
                   ` (16 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Depending on whichever quirks a driver applies, what the minimum and
maximum display-supported refresh rates for VRR are is not always
obvious to userspace.

To rectify this, add two new read-only connector properties. These
properties specify the smallest, and respectively the largest, integer
refresh rate in Hertz that the current mode is capable of displaying
with Variable Refresh Rate.

Due to how messy and chicken-and-egg it would be to implement it, this
does not consider the fractional rates below VRR min that might be
allowed, the CinemaVRR rates that might be allowed below VRRmin on HDMI
displays with this feature if userspace ends up presenting fixed rate,
or the QMS range.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_helper.c | 55 +++++++++++++++++++++++++
 drivers/gpu/drm/drm_connector.c           | 67 +++++++++++++++++++++++++++++++
 include/drm/display/drm_hdmi_helper.h     |  3 ++
 include/drm/drm_connector.h               | 30 ++++++++++++++
 4 files changed, 155 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_helper.c b/drivers/gpu/drm/display/drm_hdmi_helper.c
index f85cce3d6254..8f325d5e496d 100644
--- a/drivers/gpu/drm/display/drm_hdmi_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_helper.c
@@ -730,3 +730,58 @@ int drm_connector_hdmi_sync_scdc(struct drm_connector *connector, bool plugged,
 	return ret;
 }
 EXPORT_SYMBOL(drm_connector_hdmi_sync_scdc);
+
+/**
+ * drm_connector_hdmi_update_vrr_limits - update VRR display limit properties
+ * @conn: pointer to the &struct drm_connector to set the properties on
+ * @mode: pointer to the &struct drm_display_mode to consider while updating
+ *
+ * Update the "vrr_display_min" and "vrr_display_max" properties on @conn based
+ * on its HDMI sink info and the current mode. Does not consider any display
+ * protocol other than HDMI.
+ *
+ * Will set both min and max to 0 if max happens to end up below min, which can
+ * happen with CNMVRR==0 and a lower refresh rate @mode.
+ *
+ * Return: %0 on success, negative errno otherwise
+ */
+int drm_connector_hdmi_update_vrr_limits(struct drm_connector *conn,
+					 const struct drm_display_mode *mode)
+{
+	struct drm_mode_object *obj = &conn->base;
+	const struct drm_display_info *info;
+	u16 min_hz, max_hz;
+
+	if (!conn->vrr_display_min_property || !conn->vrr_display_max_property)
+		return -ENOENT;
+
+	info = &conn->display_info;
+
+	if (!info->is_hdmi)
+		return -EINVAL;
+
+	if (conn->status != connector_status_connected ||
+	    !info->hdmi.vrr_cap.supported) {
+		drm_object_property_set_value(obj, conn->vrr_display_min_property, 0);
+		drm_object_property_set_value(obj, conn->vrr_display_max_property, 0);
+
+		return 0;
+	}
+
+	min_hz = info->hdmi.vrr_cap.vrr_min;
+
+	if (info->hdmi.vrr_cap.cnmvrr)
+		max_hz = info->hdmi.vrr_cap.vrr_max;
+	else
+		max_hz = min(drm_mode_vrefresh(mode), info->hdmi.vrr_cap.vrr_max);
+
+	/* VRR is impossible in the current mode */
+	if (max_hz <= min_hz)
+		min_hz = max_hz = 0;
+
+	drm_object_property_set_value(obj, conn->vrr_display_min_property, min_hz);
+	drm_object_property_set_value(obj, conn->vrr_display_max_property, max_hz);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_hdmi_update_vrr_limits);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 33c4195db58b..21222214a843 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -2641,6 +2641,20 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *	numerator/denominator pair of a frame rate to set as the maximum VRR
  *	target rate. If :ref:`VRR_MAX_NUMERATOR <VRR-MAX-NUMERATOR>` is not
  *	zero, it must be non-zero.
+ *
+ * "vrr_display_min":
+ *	Optional &drm_connector property that expresses the lowest integer
+ *	frame rate in Hertz that the display can achieve in the current mode
+ *	with VRR. It is highly likely the display can represent rational rates
+ *	below this value (e.g. 24/1.001), which an atomic test commit with the
+ *	limiters set appropriately will reveal. This minimum is assuming
+ *	non-constant frame intervals, i.e. does not consider features like
+ *	CinemaVRR for HDMI.
+ *
+ * "vrr_display_max":
+ *	Optional &drm_connector property that expresses the highest integer
+ *	frame rate in Hertz that the display can achieve in the current mode
+ *	with VRR.
  */
 
 /**
@@ -2737,6 +2751,59 @@ int drm_connector_attach_vrr_display_mode_property(struct drm_connector *connect
 }
 EXPORT_SYMBOL(drm_connector_attach_vrr_display_mode_property);
 
+/**
+ * drm_connector_attach_vrr_display_limits_properties - creates and attaches VRR
+ * display limit properties
+ * @connector: pointer to the &struct drm_connector to attach the properties to
+ *
+ * Creates and attaches the "vrr_display_min" and "vrr_display_max" properties
+ * on @connector. Will either create and attach both, or on failure, neither,
+ * but never just one of the two, unless a connector with just one of the two
+ * already created is passed in.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure
+ */
+int drm_connector_attach_vrr_display_limits_properties(
+	struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_property *min_p = NULL, *max_p = NULL;
+
+	if (!connector->vrr_display_min_property) {
+		min_p = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
+						  "vrr_display_min", 0, U16_MAX);
+
+		if (!min_p)
+			return -ENOMEM;
+	}
+
+	if (!connector->vrr_display_max_property) {
+		max_p = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
+						  "vrr_display_max", 0, U16_MAX);
+
+		if (!max_p) {
+			if (min_p)
+				drm_property_destroy(dev, min_p);
+
+			return -ENOMEM;
+		}
+	}
+
+	if (min_p) {
+		connector->vrr_display_min_property = min_p;
+		drm_object_attach_property(&connector->base, min_p, 0);
+	}
+
+	if (max_p) {
+		connector->vrr_display_max_property = max_p;
+		drm_object_attach_property(&connector->base, max_p, 0);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_vrr_display_limits_properties);
+
 /**
  * drm_connector_attach_scaling_mode_property - attach atomic scaling mode property
  * @connector: connector to attach scaling mode property on.
diff --git a/include/drm/display/drm_hdmi_helper.h b/include/drm/display/drm_hdmi_helper.h
index 2cdd6e3bc090..dd96885bb592 100644
--- a/include/drm/display/drm_hdmi_helper.h
+++ b/include/drm/display/drm_hdmi_helper.h
@@ -50,4 +50,7 @@ int
 drm_connector_hdmi_sync_scdc(struct drm_connector *connector, bool plugged,
 			     struct drm_modeset_acquire_ctx *ctx);
 
+int drm_connector_hdmi_update_vrr_limits(struct drm_connector *conn,
+					 const struct drm_display_mode *mode);
+
 #endif
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 5b6920c1a996..c0865a2547fd 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -2495,6 +2495,34 @@ struct drm_connector {
 	 */
 	struct drm_property *vrr_display_mode_property;
 
+	/**
+	 * @vrr_display_min_property: Optional read-only property to help
+	 * userspace query the minimum supported VRR refresh rate in Hz by the
+	 * display.
+	 *
+	 * Can be attached to a connector by calling
+	 * drm_connector_attach_vrr_display_limits_properties().
+	 *
+	 * Drivers should update it through drm_object_property_set_value()
+	 * or through the helper drm_connector_hdmi_update_vrr_limits() for HDMI
+	 * connectors using standard HDMI VRR.
+	 */
+	struct drm_property *vrr_display_min_property;
+
+	/**
+	 * @vrr_display_max_property: Optional read-only property to help
+	 * userspace query the maximum supported VRR refresh rate in Hz by the
+	 * display.
+	 *
+	 * Can be attached to a connector by calling
+	 * drm_connector_attach_vrr_display_limits_properties().
+	 *
+	 * Drivers should update it through drm_object_property_set_value()
+	 * or through the helper drm_connector_hdmi_update_vrr_limits() for HDMI
+	 * connectors using standard HDMI VRR.
+	 */
+	struct drm_property *vrr_display_max_property;
+
 	/**
 	 * @colorspace_property: Connector property to set the suitable
 	 * colorspace supported by the sink.
@@ -2927,6 +2955,8 @@ int drm_connector_attach_vrr_capable_property(
 		struct drm_connector *connector);
 int drm_connector_attach_vrr_display_mode_property(struct drm_connector *connector,
 						   unsigned int supported_vrr_modes);
+int drm_connector_attach_vrr_display_limits_properties(
+		struct drm_connector *connector);
 void drm_connector_attach_panel_type_property(struct drm_connector *connector);
 int drm_connector_attach_broadcast_rgb_property(struct drm_connector *connector);
 int drm_connector_attach_colorspace_property(struct drm_connector *connector);

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 11/27] drm: Implement VRR rate limiting
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (9 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 10/27] drm/connector: Add vrr_display_{min,max} connector properties Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 12/27] drm/rockchip: dw_hdmi_qp: Add VRR support Nicolas Frattaroli
                   ` (15 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Currently, VRR is allowed to use the entire range advertised by the
sink's EDID. Userspace may find it desirable, however, to limit this
range, or set a fixed target rate entirely. The latter is mainly useful
for multimedia playback applications, where judder can be avoided by
matching the content's frame rate precisely.

With the new VRR target frame rate properties, the necessary
infrastructure is in place to allow implementing this.

Add the necessary logic to hdmi_validate_vrr, as well as the VRR
helpers. Since not every target rate can be reached precisely, the
per-frame error is calculated, and dithered one row at a time across
subsequent frames. This means some frames are one vtotal larger than
others, so that averaged over the frames, the desired target rate is
achieved. Such dithering is explicitly permitted by HDMI, and is still
considered a constant target frame rate.

Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 134 +++++++++++++++++++++---
 drivers/gpu/drm/drm_crtc_helper.c               |  34 +++++-
 include/drm/drm_crtc.h                          |   3 +
 include/drm/drm_crtc_helper.h                   |   2 +
 4 files changed, 156 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index ce680eaa5047..846f7a72fddc 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -5,6 +5,7 @@
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_modes.h>
 #include <drm/drm_print.h>
@@ -1051,15 +1052,60 @@ static int hdmi_calculate_vtotal(const struct drm_display_mode *mode,
 	return 0;
 }
 
+static int hdmi_validate_vrr_limits(struct drm_crtc_state *crtc_state, u16 vrr_min)
+{
+	struct drm_crtc_vrr_state *vrr_state = &crtc_state->vrr_state;
+	const struct drm_display_mode *mode = &crtc_state->mode;
+	u32 limit_d;
+	u8 round;
+	int ret;
+
+	/* No limiter, don't need to validate, but reset the error */
+	if (!drm_crtc_helper_vrr_is_fixed_rate(vrr_state)) {
+		vrr_state->per_frame_err = 0;
+		vrr_state->cur_err = 0;
+		return 0;
+	}
+
+	/* Don't allow rates equal to or below the minimum VRR rate / 1.001 */
+	if (cmp_fraction(vrr_state->vrr_min_n, vrr_state->vrr_min_d,
+			 vrr_min * 1000, 1001) < 0)
+		return -EINVAL;
+
+	if (check_mul_overflow(mode->crtc_htotal, vrr_state->vrr_min_n, &limit_d))
+		return -ERANGE;
+
+	ret = hdmi_calculate_vtotal(&crtc_state->mode, vrr_state->vrr_min_n,
+				    vrr_state->vrr_min_d, &vrr_state->base_vtotal,
+				    &vrr_state->denom, &vrr_state->per_frame_err);
+	if (ret)
+		return ret;
+
+	/* Allow rounding up for the vtotal check below */
+	if (vrr_state->per_frame_err >= vrr_state->denom / 2)
+		round = 1;
+	else
+		round = 0;
+
+	if (vrr_state->base_vtotal + round < mode->crtc_vtotal)
+		return -EINVAL;
+
+	return 0;
+}
+
 static int hdmi_validate_vrr(struct drm_connector *connector,
 			     struct drm_atomic_commit *state)
 {
 	struct drm_connector_state *new_conn_state =
 		drm_atomic_get_new_connector_state(state, connector);
+	struct drm_connector_state *old_conn_state =
+		drm_atomic_get_old_connector_state(state, connector);
 	struct drm_crtc_state *new_crtc_state =
 		drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
 	struct drm_crtc_vrr_state *vrr_state =
 		&new_crtc_state->vrr_state;
+	struct drm_crtc_vrr_state *old_vrr_state;
+	struct drm_crtc_state *old_crtc_state = NULL;
 	struct drm_display_info *info = &connector->display_info;
 	const struct drm_display_mode *mode = &new_crtc_state->mode;
 	struct drm_device *dev = connector->dev;
@@ -1068,10 +1114,14 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 	u32 min_vfreq_d = 1;
 	u32 max_vfreq_n = info->hdmi.vrr_cap.vrr_max;
 	u32 max_vfreq_d = 1;
+	bool is_fixed;
 
 	if (!new_crtc_state->vrr_enabled)
 		return 0;
 
+	if (old_conn_state)
+		old_crtc_state = drm_atomic_get_old_crtc_state(state, old_conn_state->crtc);
+
 	if (!info->hdmi.vrr_cap.supported)
 		return -EOPNOTSUPP;
 
@@ -1087,6 +1137,21 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 	if (!max_vfreq_n || max_vfreq_n > mode_refresh)
 		max_vfreq_n = mode_refresh;
 
+	is_fixed = drm_crtc_helper_vrr_is_fixed_rate(vrr_state);
+	if (is_fixed && info->hdmi.vrr_cap.cinema_vrr)
+		min_vfreq_n = 24;
+
+	/* If the VRR target changed, make sure to reset the error accumulator */
+	if (old_crtc_state) {
+		old_vrr_state = &old_crtc_state->vrr_state;
+		if (drm_crtc_helper_vrr_is_fixed_rate(old_vrr_state) != is_fixed ||
+		    cmp_fraction(vrr_state->vrr_min_n, vrr_state->vrr_min_d,
+				 old_vrr_state->vrr_min_n, old_vrr_state->vrr_min_d) ||
+		    cmp_fraction(vrr_state->vrr_max_n, vrr_state->vrr_max_d,
+				 old_vrr_state->vrr_max_n, old_vrr_state->vrr_max_d))
+			vrr_state->cur_err = 0;
+	}
+
 	if (!min_vfreq_n) {
 		drm_dbg_kms(dev, "Could not get minimum VRR rate from sink\n");
 		return -EINVAL;
@@ -1098,25 +1163,64 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 		return -EINVAL;
 	}
 
-	if (cmp_fraction(min_vfreq_n, min_vfreq_d, max_vfreq_n, max_vfreq_d) > 0) {
-		drm_dbg_kms(dev, "Target max (%u/%u) > target min (%u/%u)\n",
-			    max_vfreq_n, max_vfreq_d, min_vfreq_n, min_vfreq_d);
-		return -EINVAL;
-	}
-
-	ret = hdmi_calculate_vtotal(mode, max_vfreq_n, max_vfreq_d,
-				    &vrr_state->base_vtotal, NULL, NULL);
+	ret = hdmi_validate_vrr_limits(new_crtc_state, min_vfreq_n);
 	if (ret) {
-		drm_dbg_kms(dev, "Couldn't calculate base_vtotal: %pe\n", ERR_PTR(ret));
+		drm_dbg_kms(dev, "Invalid VRR fixed target rate: %pe\n", ERR_PTR(ret));
 		return ret;
 	}
 
-	/*
-	 * Even at its fastest, it can't go faster than the mode, so
-	 * clamp to avoid imprecisely rounded mode_refresh values we
-	 * can't do anything about from ruining our day.
-	 */
-	vrr_state->base_vtotal = max(vrr_state->base_vtotal, mode->crtc_vtotal);
+	if (!is_fixed) {
+		vrr_state->dynamic = true;
+
+		if (vrr_state->vrr_min_n) {
+			if (!vrr_state->vrr_min_d)
+				return -EINVAL;
+
+			if (cmp_fraction(vrr_state->vrr_min_n, vrr_state->vrr_min_d,
+					 min_vfreq_n * 1000, 1001) < 0) {
+				drm_dbg_kms(dev, "Target min (%u/%u) < supported min (%u/%u)\n",
+					    vrr_state->vrr_min_n, vrr_state->vrr_min_d,
+					    min_vfreq_n, min_vfreq_d);
+				return -EINVAL;
+			}
+			min_vfreq_n = vrr_state->vrr_min_n;
+			min_vfreq_d = vrr_state->vrr_min_d;
+		}
+		if (vrr_state->vrr_max_n) {
+			if (!vrr_state->vrr_max_d)
+				return -EINVAL;
+
+			if (cmp_fraction(vrr_state->vrr_max_n, vrr_state->vrr_max_d,
+					 max_vfreq_n, max_vfreq_d) > 0) {
+				drm_dbg_kms(dev, "Target max (%u/%u) > supported max (%u/%u)\n",
+					    vrr_state->vrr_max_n, vrr_state->vrr_max_d,
+					    max_vfreq_n, max_vfreq_d);
+				return -EINVAL;
+			}
+			max_vfreq_n = vrr_state->vrr_max_n;
+			max_vfreq_d = vrr_state->vrr_max_d;
+		}
+		if (cmp_fraction(min_vfreq_n, min_vfreq_d, max_vfreq_n, max_vfreq_d) > 0) {
+			drm_dbg_kms(dev, "Target max (%u/%u) > target min (%u/%u)\n",
+				    max_vfreq_n, max_vfreq_d, min_vfreq_n, min_vfreq_d);
+			return -EINVAL;
+		}
+
+		ret = hdmi_calculate_vtotal(mode, max_vfreq_n, max_vfreq_d,
+					    &vrr_state->base_vtotal, NULL, NULL);
+		if (ret) {
+			drm_dbg_kms(dev, "Couldn't calculate base_vtotal: %pe\n", ERR_PTR(ret));
+			return ret;
+		}
+
+		/*
+		 * Even at its fastest, it can't go faster than the mode, so
+		 * clamp to avoid imprecisely rounded mode_refresh values we
+		 * can't do anything about from ruining our day.
+		 */
+		vrr_state->base_vtotal = max(vrr_state->base_vtotal,
+					     mode->crtc_vtotal);
+	}
 
 	if (cmp_fraction(min_vfreq_n, min_vfreq_d, mode_refresh, 1) >= 0) {
 		/* refresh <= VRR min, don't do VRR vtotal adjustment */
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
index 4ffb3b0b7878..dd696f0c5016 100644
--- a/drivers/gpu/drm/drm_crtc_helper.c
+++ b/drivers/gpu/drm/drm_crtc_helper.c
@@ -838,6 +838,22 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set,
 }
 EXPORT_SYMBOL(drm_crtc_helper_set_config);
 
+bool drm_crtc_helper_vrr_is_fixed_rate(const struct drm_crtc_vrr_state *vrr)
+{
+	if (!vrr->vrr_min_n)
+		return false;
+
+	if (!vrr->vrr_min_d || !vrr->vrr_max_d)
+		return false;
+
+	if (mul_u32_u32(vrr->vrr_min_n, vrr->vrr_max_d) ==
+	    mul_u32_u32(vrr->vrr_min_d, vrr->vrr_max_n))
+		return true;
+
+	return false;
+}
+EXPORT_SYMBOL(drm_crtc_helper_vrr_is_fixed_rate);
+
 /**
  * drm_crtc_helper_vrr_flush - Update VRR state on frame ready
  * @crtc_state: pointer to &struct drm_crtc_state of CRTC that has a new frame ready
@@ -864,6 +880,9 @@ bool drm_crtc_helper_vrr_flush(struct drm_crtc_state *crtc_state)
 		return false;
 	}
 
+	if (drm_crtc_helper_vrr_is_fixed_rate(vrr))
+		return false;
+
 	if (vrr->cur_vtotal == vrr->base_vtotal)
 		return false;
 
@@ -889,10 +908,21 @@ bool drm_crtc_helper_vrr_vsync(struct drm_crtc_state *crtc_state)
 	struct drm_crtc_vrr_state *vrr = &crtc_state->vrr_state;
 	u16 old_vtotal = vrr->cur_vtotal;
 
-	if (!crtc_state->vrr_enabled)
+	if (!crtc_state->vrr_enabled) {
 		vrr->cur_vtotal = 0;
-	else
+		vrr->cur_err = 0;
+	} else if (drm_crtc_helper_vrr_is_fixed_rate(vrr)) {
+		if (vrr->cur_err >= (int) (vrr->denom / 2)) {
+			vrr->cur_err -= vrr->denom;
+			vrr->cur_vtotal = vrr->base_vtotal + 1;
+		} else {
+			vrr->cur_vtotal = vrr->base_vtotal;
+		}
+
+		vrr->cur_err += vrr->per_frame_err;
+	} else {
 		vrr->cur_vtotal = vrr->max_vtotal;
+	}
 
 	if (abs_diff(vrr->cur_vtotal, old_vtotal) > 1)
 		vrr->dynamic = true;
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 9004f7c2f21e..cfd051ccbd2e 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -70,6 +70,9 @@ struct drm_crtc_vrr_state {
 	u32 vrr_min_d;
 	u32 vrr_max_n;
 	u32 vrr_max_d;
+	u32 denom;
+	u32 per_frame_err;
+	int cur_err;
 	u16 base_vtotal;
 	bool dynamic;
 };
diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h
index 0c8c11ea36dd..7d7b75045bd9 100644
--- a/include/drm/drm_crtc_helper.h
+++ b/include/drm/drm_crtc_helper.h
@@ -45,6 +45,7 @@ struct drm_framebuffer;
 struct drm_mode_set;
 struct drm_modeset_acquire_ctx;
 struct drm_crtc_state;
+struct drm_crtc_vrr_state;
 
 void drm_helper_disable_unused_functions(struct drm_device *dev);
 int drm_crtc_helper_set_config(struct drm_mode_set *set,
@@ -63,6 +64,7 @@ int drm_helper_connector_dpms(struct drm_connector *connector, int mode);
 void drm_helper_resume_force_mode(struct drm_device *dev);
 int drm_helper_force_disable_all(struct drm_device *dev);
 
+bool drm_crtc_helper_vrr_is_fixed_rate(const struct drm_crtc_vrr_state *vrr);
 bool drm_crtc_helper_vrr_flush(struct drm_crtc_state *crtc_state);
 bool drm_crtc_helper_vrr_vsync(struct drm_crtc_state *crtc_state);
 

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 12/27] drm/rockchip: dw_hdmi_qp: Add VRR support
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (10 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 11/27] drm: Implement VRR rate limiting Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 13/27] drm/rockchip: Set mode_changed on vrr_enabled change Nicolas Frattaroli
                   ` (14 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Register the vrr_capable property on the connector for RK3588, where VRR
is implemented, as well as the other VRR connector properties.

In the encoder's atomic_check callback, the vrr_capable property, if
present, is then set to true if the sink supports VRR. The display limit
properties are also updated accordingly there, using the helper.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 30 ++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
index f2a1ea8dfefa..dcde50bca36b 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
@@ -89,6 +89,9 @@
 #define RK_COLOR_FMT_YUV444		0x2
 #define RK_COLOR_FMT_YUV420		0x3
 
+#define RK_VRR_DISPLAY_MODES		(BIT(VRR_DISPLAY_MODE_GAME) | \
+					 BIT(VRR_DISPLAY_MODE_OFF))
+
 struct rockchip_hdmi_qp {
 	struct device *dev;
 	struct regmap *regmap;
@@ -195,6 +198,7 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
 					 struct drm_crtc_state *crtc_state,
 					 struct drm_connector_state *conn_state)
 {
+	struct drm_connector *connector = conn_state->connector;
 	struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
 	struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
 	union phy_configure_opts phy_cfg = {};
@@ -205,6 +209,12 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
 	if (ingest_fmt < 0)
 		return -EINVAL;
 
+	if (connector->vrr_capable_property) {
+		drm_connector_set_vrr_capable_property(connector,
+			drm_display_info_is_vrr_capable(&connector->display_info));
+		drm_connector_hdmi_update_vrr_limits(connector, &crtc_state->mode);
+	}
+
 	if (hdmi->tmds_char_rate == conn_state->hdmi.tmds_char_rate &&
 	    s->output_bpc == conn_state->hdmi.output_bpc &&
 	    s->bus_format == ingest_fmt)
@@ -552,6 +562,7 @@ struct rockchip_hdmi_qp_cfg {
 	unsigned int port_ids[MAX_HDMI_PORT_NUM];
 	const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops;
 	const struct dw_hdmi_qp_phy_ops *phy_ops;
+	bool supports_vrr;
 };
 
 static const struct rockchip_hdmi_qp_cfg rk3576_hdmi_cfg = {
@@ -571,6 +582,7 @@ static const struct rockchip_hdmi_qp_cfg rk3588_hdmi_cfg = {
 	},
 	.ctrl_ops = &rk3588_hdmi_ctrl_ops,
 	.phy_ops = &rk3588_hdmi_phy_ops,
+	.supports_vrr = true,
 };
 
 static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = {
@@ -729,6 +741,24 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
 		return dev_err_probe(dev, PTR_ERR(connector),
 				     "Failed to init bridge connector\n");
 
+	if (cfg->supports_vrr) {
+		ret = drm_connector_attach_vrr_capable_property(connector);
+		if (ret)
+			return dev_err_probe(hdmi->dev, ret,
+					     "Failed to attach 'vrr_capable' property\n");
+
+		ret = drm_connector_attach_vrr_display_mode_property(connector,
+								     RK_VRR_DISPLAY_MODES);
+		if (ret)
+			return dev_err_probe(hdmi->dev, ret,
+					     "Failed to attach 'vrr_display_mode' property\n");
+
+		ret = drm_connector_attach_vrr_display_limits_properties(connector);
+		if (ret)
+			return dev_err_probe(hdmi->dev, ret,
+					     "Failed to attach VRR display limits properties\n");
+	}
+
 	platform_set_drvdata(pdev, hdmi);
 
 	return 0;

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 13/27] drm/rockchip: Set mode_changed on vrr_enabled change
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (11 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 12/27] drm/rockchip: dw_hdmi_qp: Add VRR support Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 14/27] drm/rockchip: vop2: Enable VRR Nicolas Frattaroli
                   ` (13 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

RK3588 (and likely other Rockchip hardware) requires a full modeset when
VRR is enabled in order for it to function properly.

Set mode_changed during the atomic check phase to do this.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 45588b79b3d4..e3aad75b88b4 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -13,6 +13,7 @@
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_print.h>
 
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_fb.h"
@@ -59,9 +60,26 @@ rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
 	return &afbc_fb->base;
 }
 
+static int rockchip_drm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state)
+{
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	struct drm_crtc *crtc;
+	int i;
+
+	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
+		if (old_crtc_state->vrr_enabled != new_crtc_state->vrr_enabled) {
+			new_crtc_state->mode_changed = true;
+			drm_dbg_driver(dev, "[CRTC:%d:%s] VRR changed\n",
+				       crtc->base.id, crtc->name);
+		}
+	}
+
+	return drm_atomic_helper_check(dev, state);
+}
+
 static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
 	.fb_create = rockchip_fb_create,
-	.atomic_check = drm_atomic_helper_check,
+	.atomic_check = rockchip_drm_atomic_check,
 	.atomic_commit = drm_atomic_helper_commit,
 };
 

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 14/27] drm/rockchip: vop2: Enable VRR
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (12 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 13/27] drm/rockchip: Set mode_changed on vrr_enabled change Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 15/27] drm/edid: Parse QMS flag from HDMI SCDS Nicolas Frattaroli
                   ` (12 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Use the new VRR state tracking to handle VRR front porch adjustment.

To avoid indenting too deeply, refactor the vblank handling into its own
function while at it, where scope-based cleanup helpers for the lock can
be used.

Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 65 +++++++++++++++++++++++-----
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 10 +++++
 drivers/gpu/drm/rockchip/rockchip_vop2_reg.c |  4 ++
 3 files changed, 67 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index bfb384142568..d93b49025795 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -24,6 +24,7 @@
 #include <drm/drm_atomic_uapi.h>
 #include <drm/drm_blend.h>
 #include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
 #include <linux/debugfs.h>
 #include <drm/drm_debugfs.h>
 #include <drm/drm_flip_work.h>
@@ -2011,6 +2012,25 @@ static void vop2_crtc_atomic_begin(struct drm_crtc *crtc,
 	vop2->ops->setup_overlay(vp);
 }
 
+static void vop2_vp_set_vrr_vtotal(struct vop2_video_port *vp, unsigned int vtotal)
+{
+	const u32 mask = RK3568_VP_DSP_VTOTAL_VS_END__DSP_VTOTAL |
+			 RK3568_VP_DSP_VTOTAL_VS_END__IMD;
+
+	if (!vtotal) {
+		vop2_vp_update_bits(vp, RK3568_VP_DSP_VTOTAL_VS_END,
+				    RK3568_VP_DSP_VTOTAL_VS_END__IMD, 0);
+		return;
+	}
+
+	/* Value is 13 bits at most, saturate it if too large */
+	if (vtotal > RK3568_VP_DSP_VTOTAL_VS_END__DSP_VTOTAL >> 16)
+		vtotal = RK3568_VP_DSP_VTOTAL_VS_END__DSP_VTOTAL >> 16;
+
+	vop2_vp_update_bits(vp, RK3568_VP_DSP_VTOTAL_VS_END, mask,
+			    vtotal << 16 | RK3568_VP_DSP_VTOTAL_VS_END__IMD);
+}
+
 static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
 				   struct drm_atomic_commit *state)
 {
@@ -2029,6 +2049,11 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
 
 	spin_lock_irq(&crtc->dev->event_lock);
 
+	if (crtc_state->vrr_enabled) {
+		if (drm_crtc_helper_vrr_flush(crtc_state))
+			vop2_vp_set_vrr_vtotal(vp, crtc_state->vrr_state.cur_vtotal);
+	}
+
 	if (crtc->state->event) {
 		WARN_ON(drm_crtc_vblank_get(crtc));
 		vp->event = crtc->state->event;
@@ -2371,6 +2396,30 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
 	return ret;
 }
 
+static void vop2_vp_handle_vblank(struct vop2_video_port *vp)
+{
+	struct drm_crtc *crtc = &vp->crtc;
+	struct vop2 *vop2 = vp->vop2;
+	u32 val;
+
+	guard(spinlock)(&crtc->dev->event_lock);
+
+	if (!vp->event)
+		return;
+
+	val = vop2_readl(vop2, RK3568_REG_CFG_DONE);
+
+	if (!(val & BIT(vp->id))) {
+		drm_crtc_send_vblank_event(crtc, vp->event);
+		vp->event = NULL;
+		drm_crtc_vblank_put(crtc);
+
+		if (crtc->state->vrr_enabled)
+			if (drm_crtc_helper_vrr_vsync(crtc->state))
+				vop2_vp_set_vrr_vtotal(vp, crtc->state->vrr_state.cur_vtotal);
+	}
+}
+
 static irqreturn_t vop2_isr(int irq, void *data)
 {
 	struct vop2 *vop2 = data;
@@ -2402,18 +2451,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
 
 			if (irqs & VP_INT_FS_FIELD) {
 				drm_crtc_handle_vblank(crtc);
-				spin_lock(&crtc->dev->event_lock);
-				if (vp->event) {
-					u32 val = vop2_readl(vop2, RK3568_REG_CFG_DONE);
-
-					if (!(val & BIT(vp->id))) {
-						drm_crtc_send_vblank_event(crtc, vp->event);
-						vp->event = NULL;
-						drm_crtc_vblank_put(crtc);
-					}
-				}
-				spin_unlock(&crtc->dev->event_lock);
-
+				vop2_vp_handle_vblank(vp);
 				ret = IRQ_HANDLED;
 			}
 
@@ -2635,6 +2673,9 @@ static int vop2_create_crtcs(struct vop2 *vop2)
 
 		drm_crtc_attach_background_color_property(&vp->crtc);
 
+		if (vp->data->vrr_limiter_capable)
+			drm_crtc_attach_vrr_limiter_properties(&vp->crtc);
+
 		drm_crtc_helper_add(&vp->crtc, &vop2_crtc_helper_funcs);
 		if (vop2->lut_regs) {
 			const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id];
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index ffcb39c130aa..323e2533de34 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -231,6 +231,7 @@ struct vop2_video_port_data {
 	 * @pixel_rate: pixel per cycle
 	 */
 	u8 pixel_rate;
+	bool vrr_limiter_capable;
 };
 
 struct vop2_video_port {
@@ -655,6 +656,9 @@ enum dst_factor_mode {
 
 #define RK3588_VP_DSP_CTRL__GAMMA_UPDATE_EN		BIT(22)
 
+#define RK3568_VP_DSP_VTOTAL_VS_END__DSP_VTOTAL		GENMASK(28, 16)
+#define RK3568_VP_DSP_VTOTAL_VS_END__IMD		BIT(15)
+
 #define RK3588_VP_CLK_CTRL__DCLK_OUT_DIV		GENMASK(3, 2)
 #define RK3588_VP_CLK_CTRL__DCLK_CORE_DIV		GENMASK(1, 0)
 
@@ -810,6 +814,12 @@ static inline void vop2_vp_write(struct vop2_video_port *vp, u32 offset, u32 v)
 	regmap_write(vp->vop2->map, vp->data->offset + offset, v);
 }
 
+static inline void vop2_vp_update_bits(struct vop2_video_port *vp, u32 offset,
+				       u32 mask, u32 v)
+{
+	regmap_update_bits(vp->vop2->map, vp->data->offset + offset, mask, v);
+}
+
 static inline u32 vop2_readl(struct vop2 *vop2, u32 offset)
 {
 	u32 val;
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index c44648e1bc0d..946ca420f3f5 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1063,6 +1063,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = {
 		/* hdr2sdr sdr2hdr hdr2hdr sdr2sdr */
 		.pre_scan_max_dly = { 76, 65, 65, 54 },
 		.offset = 0xc00,
+		.vrr_limiter_capable = true,
 	}, {
 		.id = 1,
 		.feature = VOP2_VP_FEATURE_OUTPUT_10BIT,
@@ -1071,6 +1072,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = {
 		.max_output = { 4096, 2304 },
 		.pre_scan_max_dly = { 76, 65, 65, 54 },
 		.offset = 0xd00,
+		.vrr_limiter_capable = true,
 	}, {
 		.id = 2,
 		.feature = VOP2_VP_FEATURE_OUTPUT_10BIT,
@@ -1079,12 +1081,14 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = {
 		.max_output = { 4096, 2304 },
 		.pre_scan_max_dly = { 52, 52, 52, 52 },
 		.offset = 0xe00,
+		.vrr_limiter_capable = true,
 	}, {
 		.id = 3,
 		.gamma_lut_len = 1024,
 		.max_output = { 2048, 1536 },
 		.pre_scan_max_dly = { 52, 52, 52, 52 },
 		.offset = 0xf00,
+		.vrr_limiter_capable = true,
 	},
 };
 

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 15/27] drm/edid: Parse QMS flag from HDMI SCDS
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (13 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 14/27] drm/rockchip: vop2: Enable VRR Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 16/27] drm/edid: Parse QMS TFR min/max flags " Nicolas Frattaroli
                   ` (11 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Thanks to a GPL'd source code release from a hardware vendor that shall
not be named, which has since been scrubbed off GitHub, it's now public
knowledge how the HDMI Forum EDID extension communicates support for QMS
("Quick Media Switching") on a sink. Bit number 6 indicates support for
QMS in the same byte that also contains the CinemaVRR flag.

Add this definition to the EDID header file, and parse it to a member of
drm_hdmi_vrr_cap.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_edid.c  | 1 +
 include/drm/drm_connector.h | 3 +++
 include/drm/drm_edid.h      | 1 +
 3 files changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 3148038747b6..d439928caacc 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6224,6 +6224,7 @@ static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db)
 	vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR;
 	vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR;
 	vrr->mdelta = db[8] & DRM_EDID_MDELTA;
+	vrr->qms_capable = db[8] & DRM_EDID_QMS;
 
 	if (cea_db_payload_len(db) < 9)
 		return;
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index c0865a2547fd..830a33fb5301 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -291,6 +291,9 @@ struct drm_hdmi_vrr_cap {
 	 * VRRmax values having correct values.
 	 */
 	bool supported;
+
+	/** @qms_capable: The sink supports Quick Media Switching */
+	bool qms_capable;
 };
 
 /**
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
index a2617aa34edf..f1e2052cb08b 100644
--- a/include/drm/drm_edid.h
+++ b/include/drm/drm_edid.h
@@ -265,6 +265,7 @@ struct detailed_timing {
 #define DRM_EDID_CNMVRR				(1 << 3)
 #define DRM_EDID_CINEMA_VRR			(1 << 4)
 #define DRM_EDID_MDELTA				(1 << 5)
+#define DRM_EDID_QMS				(1 << 6)
 #define DRM_EDID_VRR_MAX_UPPER_MASK		0xc0
 #define DRM_EDID_VRR_MAX_LOWER_MASK		0xff
 #define DRM_EDID_VRR_MIN_MASK			0x3f

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 16/27] drm/edid: Parse QMS TFR min/max flags from HDMI SCDS
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (14 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 15/27] drm/edid: Parse QMS flag from HDMI SCDS Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 17/27] drm/connector: Add "Media" VRR display mode for QMS Nicolas Frattaroli
                   ` (10 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

HDMI has two flags in the SCDS EDID extension that signal which minimum
and maximum refresh rate the sink wishes to use when doing QMS ("Quick
Media Switching").

How to parse these flags, and what they do, has been learned from the
publicly available libdisplay-info source code.

Add the definitions of where in the byte they are, the code for checking
for them, as well as two new booleans in drm_hdmi_vrr_cap to store the
result.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_edid.c  |  6 ++++++
 include/drm/drm_connector.h | 12 ++++++++++++
 include/drm/drm_edid.h      |  2 ++
 3 files changed, 20 insertions(+)

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index d439928caacc..39b5c01309e2 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6237,6 +6237,12 @@ static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db)
 
 	vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10];
 	vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100);
+
+	if (cea_db_payload_len(db) < 11)
+		return;
+
+	vrr->qms_tfr_min = db[11] & DRM_EDID_QMS_TFR_MIN;
+	vrr->qms_tfr_max = db[11] & DRM_EDID_QMS_TFR_MAX;
 }
 
 static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc,
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 830a33fb5301..4801bb2769e1 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -294,6 +294,18 @@ struct drm_hdmi_vrr_cap {
 
 	/** @qms_capable: The sink supports Quick Media Switching */
 	bool qms_capable;
+
+	/**
+	 * @qms_tfr_min: If set, the sink uses 24/1.001Hz as its minimum rate
+	 * for QMS. If unset, @drm_display_info.monitor_range.min_vfreq is used.
+	 */
+	bool qms_tfr_min;
+
+	/**
+	 * @qms_tfr_max: If set, @drm_display_info.monitor_range.max_vfreq is
+	 * used by the sink as its maximum rate for QMS. If unset, 60Hz is used.
+	 */
+	bool qms_tfr_max;
 };
 
 /**
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
index f1e2052cb08b..d339acd9ee52 100644
--- a/include/drm/drm_edid.h
+++ b/include/drm/drm_edid.h
@@ -269,6 +269,8 @@ struct detailed_timing {
 #define DRM_EDID_VRR_MAX_UPPER_MASK		0xc0
 #define DRM_EDID_VRR_MAX_LOWER_MASK		0xff
 #define DRM_EDID_VRR_MIN_MASK			0x3f
+#define DRM_EDID_QMS_TFR_MIN			(1 << 4)
+#define DRM_EDID_QMS_TFR_MAX			(1 << 5)
 
 /* DSC specific */
 #define DRM_EDID_DSC_10BPC			(1 << 0)

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 17/27] drm/connector: Add "Media" VRR display mode for QMS
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (15 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 16/27] drm/edid: Parse QMS TFR min/max flags " Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 18/27] video/hdmi: Add support for QMS in VTEM EMP packing Nicolas Frattaroli
                   ` (9 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

HDMI includes a feature called "Quick Media Switching", or QMS for
short, which builds upon the existing VRR to allow for quick switching
between a select few common frame rates.

Add a new VRR display mode, "VRR_DISPLAY_MODE_MEDIA", to the
vrr_display_mode connector property, in order to expose support for
this.

Additionally, add a boolean flag to the connector's HDMI sub-struct with
which source-side support for QMS can be indicated, as well as the QMS
tfr byte to the HDMI state sub-struct.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_connector.c |  6 ++++++
 include/drm/drm_connector.h     | 14 ++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 21222214a843..f7d619926e2d 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -1456,6 +1456,7 @@ DRM_ENUM_NAME_FN(drm_get_tv_mode_name, drm_tv_mode_enum_list)
 static const struct drm_prop_enum_list drm_vrr_mode_enum_list[] = {
 	{ VRR_DISPLAY_MODE_GAME,   "Game" },
 	{ VRR_DISPLAY_MODE_OFF,    "Off" },
+	{ VRR_DISPLAY_MODE_MEDIA,  "Media" },
 };
 DRM_ENUM_NAME_FN(drm_get_vrr_mode_name, drm_vrr_mode_enum_list)
 
@@ -2593,6 +2594,11 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *	    - Game - set display to game mode if display supports it,
  *	      regardless of CRTC state
  *	    - Off - set display to VRR disabled mode, regardless of CRTC state
+ *	    - Media - a "media" VRR mode that only supports a select number of
+ *	      common media refresh rates as fixed targets, but does so with
+ *	      possibly better display-side processing. In the HDMI display
+ *	      protocol, this is QMS (Quick Media Switching), though the mode
+ *	      enum itself is vendor-agnostic
  *
  * VRR Limiter/Target Properties
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 4801bb2769e1..41f272afbb1e 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -829,6 +829,8 @@ enum vrr_display_mode {
 	VRR_DISPLAY_MODE_GAME = 0,
 	/** @VRR_DISPLAY_MODE_OFF: Turn off VRR on the display */
 	VRR_DISPLAY_MODE_OFF,
+	/** @VRR_DISPLAY_MODE_MEDIA: Protocol-agnostic fixed rate media mode */
+	VRR_DISPLAY_MODE_MEDIA,
 	/** @VRR_DISPLAY_MODE_COUNT: Sentinel, not a valid mode, keep last */
 	VRR_DISPLAY_MODE_COUNT
 };
@@ -1241,6 +1243,12 @@ struct drm_connector_hdmi_state {
 	 * deliberately not requested by the helper.
 	 */
 	bool scrambler_needed;
+
+	/**
+	 * @qms_tfr_byte: Target frame rate byte for HDMI QMS, or 0 if QMS is
+	 * not in use.
+	 */
+	u8 qms_tfr_byte;
 };
 
 /**
@@ -2270,6 +2278,12 @@ struct drm_connector_hdmi {
 	 */
 	bool scrambler_enabled;
 
+	/**
+	 * @qms_supported: Whether the connector supports HDMI Quick Media
+	 * Switching (QMS) on the source side, irrespective of display support.
+	 */
+	bool qms_supported;
+
 	/**
 	 * @scdc_work: Work item currently used to monitor sink-side scrambling
 	 * status and retry setup if the sink resets it.

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 18/27] video/hdmi: Add support for QMS in VTEM EMP packing
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (16 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 17/27] drm/connector: Add "Media" VRR display mode for QMS Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 19/27] drm/connector: hdmi: Add QMS to VTEM EMP generation Nicolas Frattaroli
                   ` (8 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add the necessary struct members and packing code to allow the
hdmi_emp_infoframe_vtem_pack function to write VTEM packets with HDMI
Quick Media Switching (QMS) data.

Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/video/hdmi.c | 6 ++++--
 include/linux/hdmi.h | 2 ++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index ed9abc7ad228..c164f69a646a 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -1994,7 +1994,7 @@ ssize_t hdmi_emp_infoframe_vtem_pack(struct hdmi_emp_infoframe_vtem *vtem,
 	ptr[1] = BIT(7) | BIT(6);
 	ptr[2] = 0;
 
-	ptr[3] = BIT(7) | BIT(2);
+	ptr[3] = BIT(7) | BIT(2) | (vtem->qms_en ? BIT(1) : 0);
 
 	ptr[4] = 0;
 	ptr[5] = 1;
@@ -2005,10 +2005,12 @@ ssize_t hdmi_emp_infoframe_vtem_pack(struct hdmi_emp_infoframe_vtem *vtem,
 
 	/* This is where the VTEM part starts */
 	ptr[10] = (vtem->fva_factor_m1 << 4) |
+		  (vtem->qms_en ? BIT(2) : 0) |
 		  (vtem->m_const ? BIT(1) : 0) |
 		  (vtem->game_vrr_en ? BIT(0) : 0);
 	ptr[11] = vtem->base_vfront;
-	ptr[12] = vtem->base_refresh_rate >> 8 & 0x3;
+	ptr[12] = (vtem->next_tfr << 3) |
+		  (vtem->base_refresh_rate >> 8 & 0x3);
 	ptr[13] = vtem->base_refresh_rate & 0xFF;
 
 	return 14;
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index 71e2b36896af..8eaa127f0cb5 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -243,10 +243,12 @@ struct hdmi_emp_infoframe_vtem {
 	unsigned char length;
 
 	bool game_vrr_en;
+	bool qms_en;
 	bool m_const;
 	unsigned char fva_factor_m1;
 	unsigned char base_vfront;
 	u16 base_refresh_rate;
+	u8 next_tfr;
 };
 
 void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 19/27] drm/connector: hdmi: Add QMS to VTEM EMP generation
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (17 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 18/27] video/hdmi: Add support for QMS in VTEM EMP packing Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 20/27] drm/connector: hdmi: Add QMS state validation and computation Nicolas Frattaroli
                   ` (7 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

If userspace indicates that VRR mode "Media" is desired and the
connector supports QMS, set the QMS related fields in the VTEM EMP
struct based on the connector state.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index 846f7a72fddc..407ad28c649f 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -890,11 +890,18 @@ static int hdmi_generate_emp_infoframe_vtem(const struct drm_connector *connecto
 		break;
 	case VRR_DISPLAY_MODE_OFF:
 		break;
+	case VRR_DISPLAY_MODE_MEDIA:
+		if (!connector->hdmi.qms_supported)
+			return -EOPNOTSUPP;
+		vtem->qms_en = true;
+		vtem->next_tfr = conn_state->hdmi.qms_tfr_byte;
+		vtem->m_const = !vrr->dynamic;
+		break;
 	default:
 		return -EINVAL;
 	}
 
-	if (vtem->game_vrr_en && !avi_vic) {
+	if ((vtem->game_vrr_en || vtem->qms_en) && !avi_vic) {
 		vtem->base_refresh_rate = drm_mode_vrefresh(&crtc_state->mode);
 		vfront = crtc_state->adjusted_mode.crtc_vsync_start -
 			 crtc_state->adjusted_mode.crtc_vdisplay;

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 20/27] drm/connector: hdmi: Add QMS state validation and computation
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (18 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 19/27] drm/connector: hdmi: Add QMS to VTEM EMP generation Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 21/27] drm/rockchip: dw_hdmi_qp: Add QMS support Nicolas Frattaroli
                   ` (6 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add the necessary code to the HDMI state helpers' hdmi_validate_vrr
function in order to check and derive the state necessary for HDMI Quick
Media Switching (QMS) support.

Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c | 112 +++++++++++++++++++++++-
 1 file changed, 109 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index 407ad28c649f..ba290eea7390 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -2,6 +2,7 @@
 
 #include <linux/debugfs.h>
 #include <linux/export.h>
+#include <linux/gcd.h>
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_connector.h>
@@ -954,6 +955,26 @@ hdmi_generate_infoframes(const struct drm_connector *connector,
 	return 0;
 }
 
+static const struct {
+	u8 tfr_byte;
+	u32 rate_n;
+	u32 rate_d;
+} hdmi_qms_tfrs[] = {
+	{ 1, 24000, 1001 },
+	{ 2, 24, 1 },
+	{ 3, 25, 1 },
+	{ 4, 30000, 1001 },
+	{ 5, 30, 1 },
+	{ 6, 48000, 1001 },
+	{ 7, 48, 1 },
+	{ 8, 50, 1 },
+	{ 9, 60000, 1001 },
+	{ 10, 60, 1 },
+	{ 11, 100, 1 },
+	{ 12, 120000, 1001 },
+	{ 13, 120, 1 },
+};
+
 /**
  * cmp_fraction - compares two fractional numbers
  * @a: numerator of the first fraction
@@ -992,6 +1013,66 @@ static int cmp_fraction(u32 a, u32 b, u32 c, u32 d)
 	return 0;
 }
 
+/**
+ * hdmi_validate_qms - Validate HDMI QMS state
+ * @crtc_state: pointer to a &struct drm_crtc_state to run validation on
+ * @info: pointer to a &struct drm_display_info of the accompanying connector
+ *
+ * Validate that the drm_crtc_vrr_state.vrr_min_numerator and
+ * drm_crtc_vrr_state.vrr_min_denominator are acceptable for HDMI QMS, and check
+ * that the display is HDMI QMS capable.
+ *
+ * Returns:
+ *  - Negative errno on failure
+ *  - the corresponding QMS signaling byte for the target frame rate otherwise
+ */
+static int hdmi_validate_qms(struct drm_crtc_state *crtc_state,
+			     struct drm_connector_state *conn_state,
+			     const struct drm_display_info *info)
+{
+	u32 rate_n = crtc_state->vrr_state.vrr_min_n;
+	u32 rate_d = crtc_state->vrr_state.vrr_min_d;
+	u32 min_rate_n, max_rate;
+	u32 g, i;
+
+	if (conn_state->vrr_display_mode != VRR_DISPLAY_MODE_MEDIA)
+		return 0;
+
+	if (!drm_crtc_helper_vrr_is_fixed_rate(&crtc_state->vrr_state))
+		return -EINVAL;
+
+	if (info->hdmi.vrr_cap.qms_tfr_min)
+		min_rate_n = 24000;
+	else
+		min_rate_n = info->hdmi.vrr_cap.vrr_min * 1000;
+
+	if (info->hdmi.vrr_cap.qms_tfr_max)
+		max_rate = info->hdmi.vrr_cap.vrr_max;
+	else
+		max_rate = 60;
+
+	g = gcd(rate_n, rate_d);
+	if (g != 1) {
+		rate_n /= g;
+		rate_d /= g;
+	}
+
+	if (!rate_d)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(hdmi_qms_tfrs); i++) {
+		if (cmp_fraction(min_rate_n, 1001, hdmi_qms_tfrs[i].rate_n,
+				 hdmi_qms_tfrs[i].rate_d) > 0)
+			continue;
+		if (rate_n == hdmi_qms_tfrs[i].rate_n &&
+		    rate_d == hdmi_qms_tfrs[i].rate_d &&
+		    cmp_fraction(rate_n, rate_d, max_rate, 1) <= 0)
+			return hdmi_qms_tfrs[i].tfr_byte;
+	}
+
+	return -EINVAL;
+}
+
 static int hdmi_calculate_vtotal(const struct drm_display_mode *mode,
 				 u32 rate_n, u32 rate_d, u16 *out_vtotal,
 				 u32 *out_denom, u32 *out_frame_err)
@@ -1129,8 +1210,17 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 	if (old_conn_state)
 		old_crtc_state = drm_atomic_get_old_crtc_state(state, old_conn_state->crtc);
 
-	if (!info->hdmi.vrr_cap.supported)
-		return -EOPNOTSUPP;
+	switch (new_conn_state->vrr_display_mode) {
+	case VRR_DISPLAY_MODE_GAME:
+		if (!info->hdmi.vrr_cap.supported)
+			return -EOPNOTSUPP;
+		break;
+	case VRR_DISPLAY_MODE_MEDIA:
+		if (!info->hdmi.vrr_cap.qms_capable ||
+		    !connector->hdmi.qms_supported)
+			return -EOPNOTSUPP;
+		break;
+	}
 
 	mode_refresh = drm_mode_vrefresh(mode);
 
@@ -1145,7 +1235,10 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 		max_vfreq_n = mode_refresh;
 
 	is_fixed = drm_crtc_helper_vrr_is_fixed_rate(vrr_state);
-	if (is_fixed && info->hdmi.vrr_cap.cinema_vrr)
+	if (is_fixed && ((info->hdmi.vrr_cap.qms_tfr_min &&
+			  new_conn_state->vrr_display_mode == VRR_DISPLAY_MODE_MEDIA &&
+			  connector->hdmi.qms_supported && info->hdmi.vrr_cap.qms_capable) ||
+			 info->hdmi.vrr_cap.cinema_vrr))
 		min_vfreq_n = 24;
 
 	/* If the VRR target changed, make sure to reset the error accumulator */
@@ -1176,6 +1269,19 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 		return ret;
 	}
 
+	ret = hdmi_validate_qms(new_crtc_state, new_conn_state, info);
+	if (ret < 0) {
+		drm_dbg_kms(dev, "Invalid QMS rate: %pe\n", ERR_PTR(ret));
+		return ret;
+	}
+
+	new_conn_state->hdmi.qms_tfr_byte = ret;
+	if (!old_conn_state ||
+	    new_conn_state->hdmi.qms_tfr_byte != old_conn_state->hdmi.qms_tfr_byte)
+		vrr_state->dynamic = true;
+	else
+		vrr_state->dynamic = false;
+
 	if (!is_fixed) {
 		vrr_state->dynamic = true;
 

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 21/27] drm/rockchip: dw_hdmi_qp: Add QMS support
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (19 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 20/27] drm/connector: hdmi: Add QMS state validation and computation Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 22/27] drm/tests: hdmi: Add "Game Mode" VRR tests Nicolas Frattaroli
                   ` (5 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add the "Media" VRR display mode to the set of VRR display modes being
registered, and set the qms_supported flag in the connector's HDMI
sub-struct. The actual QMS functionality itself is implemented in the
common HDMI state helpers.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
index dcde50bca36b..215cf746dc41 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
@@ -90,7 +90,8 @@
 #define RK_COLOR_FMT_YUV420		0x3
 
 #define RK_VRR_DISPLAY_MODES		(BIT(VRR_DISPLAY_MODE_GAME) | \
-					 BIT(VRR_DISPLAY_MODE_OFF))
+					 BIT(VRR_DISPLAY_MODE_OFF) | \
+					 BIT(VRR_DISPLAY_MODE_MEDIA))
 
 struct rockchip_hdmi_qp {
 	struct device *dev;
@@ -753,6 +754,8 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
 			return dev_err_probe(hdmi->dev, ret,
 					     "Failed to attach 'vrr_display_mode' property\n");
 
+		connector->hdmi.qms_supported = true;
+
 		ret = drm_connector_attach_vrr_display_limits_properties(connector);
 		if (ret)
 			return dev_err_probe(hdmi->dev, ret,

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 22/27] drm/tests: hdmi: Add "Game Mode" VRR tests
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (20 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 21/27] drm/rockchip: dw_hdmi_qp: Add QMS support Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 23/27] drm/tests: hdmi: Add Fixed/Constrained rate " Nicolas Frattaroli
                   ` (4 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add a few tests to verify "Game Mode" VRR functionality.

One test checks that hdmi_validate_vrr succeeds if the sink is VRR
capable and VRR is enabled.

Another test checks that hdmi_validate_vrr fails if the sink isn't VRR
capable, but VRR is nevertheless enabled.

Yet another test checks that flush/vsync have cur_vtotal bounce between
base_vtotal and max_vtotal.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 263 +++++++++++++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.c             | 140 +++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.h             |   1 +
 3 files changed, 404 insertions(+)

diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index 9953f5e661ce..def414085069 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -4,9 +4,12 @@
  * Kunit test for drm_hdmi_state_helper functions
  */
 
+#include <linux/cleanup.h>
+
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_state_helper.h>
 #include <drm/drm_atomic_uapi.h>
+#include <drm/drm_crtc_helper.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_connector.h>
@@ -26,6 +29,15 @@
 
 #include "drm_kunit_edid.h"
 
+static void drop_fini_modeset(struct drm_modeset_acquire_ctx *ctx)
+{
+	drm_modeset_drop_locks(ctx);
+	drm_modeset_acquire_fini(ctx);
+}
+
+DEFINE_GUARD(modeset_lock, struct drm_modeset_acquire_ctx *,
+	     drm_modeset_acquire_init(_T, 0), drop_fini_modeset(_T));
+
 struct drm_atomic_helper_connector_hdmi_priv {
 	struct drm_device drm;
 	struct drm_plane *plane;
@@ -289,6 +301,11 @@ __connector_hdmi_init(struct kunit *test,
 	drm_connector_helper_add(conn, &dummy_connector_helper_funcs);
 	drm_connector_attach_encoder(conn, enc);
 
+	ret = drm_connector_attach_vrr_display_mode_property(conn,
+							     BIT(VRR_DISPLAY_MODE_GAME) |
+							     BIT(VRR_DISPLAY_MODE_OFF));
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
 	drm_mode_config_reset(drm);
 
 	if (edid_data && edid_len) {
@@ -3424,11 +3441,257 @@ static struct kunit_suite drm_atomic_helper_connector_hdmi_infoframes_test_suite
 	.test_cases	= drm_atomic_helper_connector_hdmi_infoframes_tests,
 };
 
+static void allocate_state_for_vrr_test(struct kunit *test,
+					struct drm_atomic_helper_connector_hdmi_priv *priv,
+					struct drm_modeset_acquire_ctx *ctx,
+					struct drm_display_mode *mode,
+					struct drm_atomic_commit **out_state)
+{
+	struct drm_atomic_commit *state;
+	int ret;
+
+retry:
+	ret = drm_kunit_helper_enable_crtc_connector(test, &priv->drm, priv->crtc,
+						     &priv->connector, mode, ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+	*out_state = state;
+}
+
+
+static void prepare_state_for_vrr_test(struct kunit *test,
+				       struct drm_atomic_helper_connector_hdmi_priv *priv,
+				       struct drm_modeset_acquire_ctx *ctx,
+				       struct drm_atomic_commit *state,
+				       struct drm_crtc_state **out_crtc_state)
+{
+	struct drm_connector_state *conn_state;
+	struct drm_crtc_state *crtc_state;
+	int ret;
+
+retry_state:
+	conn_state = drm_atomic_get_connector_state(state, &priv->connector);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(ctx);
+		if (!ret)
+			goto retry_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	crtc_state = drm_atomic_get_crtc_state(state, priv->crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(ctx);
+		if (!ret)
+			goto retry_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	*out_crtc_state = crtc_state;
+}
+
+/*
+ * Check that on a VRR-capable sink with VRR enabled, an atomic commit works.
+ */
+static void drm_test_check_hdmi_vrr(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	struct drm_crtc *crtc;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+	KUNIT_EXPECT_GE(test, crtc_state->vrr_state.base_vtotal, preferred->crtc_vtotal);
+	KUNIT_EXPECT_GE(test, crtc_state->vrr_state.max_vtotal, preferred->crtc_vtotal);
+}
+
+/*
+ * Check that on a sink that's not VRR capable, enabling VRR and doing an atomic
+ * commit fails.
+ */
+static void drm_test_check_hdmi_vrr_sink_fail(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, -EOPNOTSUPP);
+}
+
+/*
+ * Check that the drm_crtc_helper_vrr_flush() and drm_crtc_helper_vrr_vsync()
+ * helpers return the expected values on a VRR-capable sink with VRR enabled.
+ */
+static void drm_test_check_hdmi_vrr_flush_vsync(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(&priv->connector);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+	KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.base_vtotal, preferred->crtc_vtotal);
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.max_vtotal,
+			preferred->crtc_vtotal * (drm_mode_vrefresh(preferred) / 30));
+
+	KUNIT_EXPECT_TRUE(test, drm_crtc_helper_vrr_flush(crtc_state));
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+			crtc_state->vrr_state.base_vtotal);
+
+	/*
+	 * Invoking drm_crtc_helper_vrr_flush() again should not change
+	 * cur_vtotal, meaning it should return false.
+	 */
+	KUNIT_EXPECT_FALSE(test, drm_crtc_helper_vrr_flush(crtc_state));
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+			crtc_state->vrr_state.base_vtotal);
+
+	KUNIT_EXPECT_TRUE(test, drm_crtc_helper_vrr_vsync(crtc_state));
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+			crtc_state->vrr_state.max_vtotal);
+
+	/* Same as above, double invocation should leave it unchanged. */
+
+	KUNIT_EXPECT_FALSE(test, drm_crtc_helper_vrr_vsync(crtc_state));
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+			crtc_state->vrr_state.max_vtotal);
+}
+
+static struct kunit_case drm_atomic_helper_connector_hdmi_vrr_tests[] = {
+	KUNIT_CASE(drm_test_check_hdmi_vrr),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_sink_fail),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_flush_vsync),
+	{ }
+};
+
+static struct kunit_suite drm_atomic_helper_connector_hdmi_vrr_test_suite = {
+	.name		= "drm_atomic_helper_connector_hdmi_vrr",
+	.test_cases	= drm_atomic_helper_connector_hdmi_vrr_tests,
+};
+
 kunit_test_suites(
 	&drm_atomic_helper_connector_hdmi_check_test_suite,
 	&drm_atomic_helper_connector_hdmi_reset_test_suite,
 	&drm_atomic_helper_connector_hdmi_mode_valid_test_suite,
 	&drm_atomic_helper_connector_hdmi_infoframes_test_suite,
+	&drm_atomic_helper_connector_hdmi_vrr_test_suite,
 );
 
 MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.c b/drivers/gpu/drm/tests/drm_kunit_edid.c
index c807ae0d9faa..8b78a8bf063d 100644
--- a/drivers/gpu/drm/tests/drm_kunit_edid.c
+++ b/drivers/gpu/drm/tests/drm_kunit_edid.c
@@ -990,6 +990,146 @@ const unsigned char test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz[] = {
 };
 EXPORT_SYMBOL(test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz);
 
+/*
+ * Max resolution: 3840x2160@60Hz with YUV420
+ * Max BPC:        16 for all modes
+ * Max TMDS clock: <340MHz, so set to 0
+ * VRR range: 30Hz to 120Hz
+ * CinemaVRR flag is set
+ *
+ * edid-decode (hex):
+ *
+ * 00 ff ff ff ff ff ff 00 31 d8 43 00 00 00 00 00
+ * 01 24 01 03 80 60 36 78 0f ee 91 a3 54 4c 99 26
+ * 0f 50 54 20 00 00 01 01 01 01 01 01 01 01 01 01
+ * 01 01 01 01 01 01 04 74 80 18 71 38 2d 40 58 2c
+ * 45 00 c0 1c 32 00 00 1e 04 74 00 30 f2 70 5a 80
+ * b0 58 8a 00 c0 1c 32 00 00 1e 00 00 00 fc 00 54
+ * 65 73 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd
+ * 00 18 78 18 87 22 00 0a 20 20 20 20 20 20 01 7e
+ *
+ * 02 03 29 31 42 3f 5f 6d 03 0c 00 10 00 78 00 20
+ * 00 00 00 20 61 6d d8 5d c4 01 00 80 07 10 1e 78
+ * 00 00 00 e2 0e 61 e2 00 ed 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68
+ *
+ * ----------------
+ *
+ * Block 0, Base EDID:
+ *  EDID Structure Version & Revision: 1.3
+ *  Vendor & Product Identification:
+ *    Manufacturer: LNX
+ *    Model: 67
+ *    Made in: week 1 of 2026
+ *  Basic Display Parameters & Features:
+ *    Digital display
+ *    Maximum image size: 96 cm x 54 cm
+ *    Gamma: 2.20
+ *    RGB color display
+ *    Default (sRGB) color space is primary color space
+ *    First detailed timing is the preferred timing
+ *    Supports GTF timings within operating range
+ *  Color Characteristics:
+ *    Red  : 0.6396, 0.3300
+ *    Green: 0.2998, 0.5996
+ *    Blue : 0.1503, 0.0595
+ *    White: 0.3125, 0.3291
+ *  Established Timings I & II:
+ *    DMT 0x04:   640x480    59.940476 Hz   4:3     31.469 kHz     25.175000 MHz
+ *  Standard Timings: none
+ *  Detailed Timing Descriptors:
+ *    DTD 1:  1920x1080  120.000000 Hz  16:9    135.000 kHz    297.000000 MHz (960 mm x 540 mm)
+ *                 Hfront   88 Hsync  44 Hback  148 Hpol P
+ *                 Vfront    4 Vsync   5 Vback   36 Vpol P
+ *    DTD 2:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz (960 mm x 540 mm)
+ *                 Hfront  176 Hsync  88 Hback  296 Hpol P
+ *                 Vfront    8 Vsync  10 Vback   72 Vpol P
+ *    Display Product Name: 'Test EDID'
+ *    Display Range Limits:
+ *      Monitor ranges (GTF): 24-120 Hz V, 24-135 kHz H, max dotclock 340 MHz
+ *  Extension blocks: 1
+ * Checksum: 0x7e
+ *
+ * ----------------
+ *
+ * Block 1, CTA-861 Extension Block:
+ *  Revision: 3
+ *  Supports YCbCr 4:4:4
+ *  Supports YCbCr 4:2:2
+ *  Native detailed modes: 1
+ *  Video Data Block:
+ *    VIC  63:  1920x1080  120.000000 Hz  16:9    135.000 kHz    297.000000 MHz
+ *    VIC  95:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz
+ *  Vendor-Specific Data Block (HDMI), OUI 00-0C-03:
+ *    Source physical address: 1.0.0.0
+ *    DC_48bit
+ *    DC_36bit
+ *    DC_30bit
+ *    DC_Y444
+ *    Maximum TMDS clock: 0 MHz
+ *    Extended HDMI video details:
+ *  Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8:
+ *    Version: 1
+ *    SCDC Present
+ *    Supports 16-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports 12-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports 10-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports media rates below VRRmin (CinemaVRR, deprecated)
+ *    VRRmin: 30 Hz
+ *    VRRmax: 120 Hz
+ *  YCbCr 4:2:0 Video Data Block:
+ *    VIC  97:  3840x2160   60.000000 Hz  16:9    135.000 kHz    594.000000 MHz
+ *  Video Capability Data Block:
+ *    YCbCr quantization: Selectable (via AVI YQ)
+ *    RGB quantization: Selectable (via AVI Q)
+ *    PT scan behavior: Always Underscanned
+ *    IT scan behavior: Supports both over- and underscan
+ *    CE scan behavior: Always Overscanned
+ * Checksum: 0x68  Unused space in Extension Block: 86 bytes
+ *
+ * ----------------
+ *
+ * edid-decode 1.32.0
+ *
+ * Warnings:
+ *
+ * Block 1, CTA-861 Extension Block:
+ *  *** v- pretty sure this one's a bug in edid-decode ***
+ *  IT Video Formats are overscanned by default, but normally this should be underscanned.
+ *  Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8: CinemaVRR is deprecated and must be cleared.
+ *
+ * EDID conformity: PASS
+ */
+const unsigned char test_edid_hdmi_vrr[] = {
+	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x43, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0x24, 0x01, 0x03, 0x80, 0x60, 0x36, 0x78,
+	0x0f, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x20,
+	0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x74, 0x80, 0x18, 0x71, 0x38,
+	0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e,
+	0x04, 0x74, 0x00, 0x30, 0xf2, 0x70, 0x5a, 0x80, 0xb0, 0x58, 0x8a, 0x00,
+	0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54,
+	0x65, 0x73, 0x74, 0x20, 0x45, 0x44, 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20,
+	0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, 0x78, 0x18, 0x87, 0x22, 0x00, 0x0a,
+	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x7e, 0x02, 0x03, 0x29, 0x31,
+	0x42, 0x3f, 0x5f, 0x6d, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x78, 0x00, 0x20,
+	0x00, 0x00, 0x00, 0x20, 0x61, 0x6d, 0xd8, 0x5d, 0xc4, 0x01, 0x00, 0x80,
+	0x07, 0x10, 0x1e, 0x78, 0x00, 0x00, 0x00, 0xe2, 0x0e, 0x61, 0xe2, 0x00,
+	0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x68
+};
+EXPORT_SYMBOL(test_edid_hdmi_vrr);
+
 MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
 MODULE_DESCRIPTION("EDID Definitions for KUnit tests");
 MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.h b/drivers/gpu/drm/tests/drm_kunit_edid.h
index 28b4df93a555..80dc15a8bcb6 100644
--- a/drivers/gpu/drm/tests/drm_kunit_edid.h
+++ b/drivers/gpu/drm/tests/drm_kunit_edid.h
@@ -12,5 +12,6 @@ extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz[256];
 extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz[256];
 extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz[256];
 extern const unsigned char test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz[256];
+extern const unsigned char test_edid_hdmi_vrr[256];
 
 #endif // DRM_KUNIT_EDID_H_

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 23/27] drm/tests: hdmi: Add Fixed/Constrained rate VRR tests
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (21 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 22/27] drm/tests: hdmi: Add "Game Mode" VRR tests Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 24/27] drm/tests: hdmi: Add Quick Media Switching tests Nicolas Frattaroli
                   ` (3 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add tests to verify the functionality of the
VRR_{MIN,MAX}_{NUMERATOR,DENOMINATOR} properties and the HDMI state
helpers that make use of them.

This includes tests for fixed rate scenarios, as well as a test for a
constrained game mode scenario. A parametric test case checks that
various erroneous target rates are rejected.

Proper handling of CinemaVRR is tested as well.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 386 +++++++++++++++++++++
 1 file changed, 386 insertions(+)

diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index def414085069..4d9bcab57778 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -3674,10 +3674,396 @@ static void drm_test_check_hdmi_vrr_flush_vsync(struct kunit *test)
 			crtc_state->vrr_state.max_vtotal);
 }
 
+/*
+ * Check that on a VRR-capable sink with VRR enabled and a fixed target rate
+ * set, an atomic commit works, and the derived vtotals are as expected.
+ */
+static void drm_test_check_hdmi_vrr_fixed_rate(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	int mode_refresh;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.hdmi.vrr_cap.cinema_vrr);
+
+	preferred = find_preferred_mode(&priv->connector);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 40;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	/* Equivalent fraction, expressed differently to pose a hurdle */
+	crtc_state->vrr_state.vrr_max_n = 40000;
+	crtc_state->vrr_state.vrr_max_d = 1000;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+	KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.base_vtotal,
+			preferred->crtc_vtotal * mode_refresh / 40);
+
+	/* Minimum rate is 24Hz because of the CinemaVRR flag */
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.max_vtotal,
+			preferred->crtc_vtotal * mode_refresh / 24);
+}
+
+/*
+ * Check that the drm_hdmi_helper_vrr_flush() and drm_hdmi_helper_vrr_vsync()
+ * helpers return the expected values on a VRR-capable sink with VRR enabled
+ * and a fixed target set.
+ */
+static void drm_test_check_hdmi_vrr_fixed_rate_flush_vsync(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	int mode_refresh;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, priv->connector.display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(&priv->connector);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	scoped_guard(modeset_lock, &ctx) {
+		allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+		prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+		crtc_state->vrr_enabled = true;
+		crtc_state->vrr_state.vrr_min_n = 40000;
+		crtc_state->vrr_state.vrr_min_d = 1001;
+		/* Equivalent fraction, expressed differently to pose a hurdle */
+		crtc_state->vrr_state.vrr_max_n = 80000;
+		crtc_state->vrr_state.vrr_max_d = 2002;
+
+		ret = drm_atomic_commit(state);
+		if (ret == -EDEADLK) {
+			drm_atomic_commit_clear(state);
+			ret = drm_modeset_backoff(&ctx);
+			if (!ret)
+				goto retry;
+		}
+		KUNIT_ASSERT_EQ(test, ret, 0);
+
+		crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+		KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+		KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.base_vtotal,
+				mult_frac(preferred->crtc_vtotal, mode_refresh * 1000, 39960));
+
+		/* Fixed rate case: flush should never trigger a vtotal update. */
+		KUNIT_EXPECT_FALSE(test, drm_crtc_helper_vrr_flush(crtc_state));
+		KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal, 0);
+
+		/*
+		 * Invoking drm_hdmi_helper_vrr_flush() again should not change
+		 * cur_vtotal, meaning it should return false.
+		 */
+		KUNIT_EXPECT_FALSE(test, drm_crtc_helper_vrr_flush(crtc_state));
+		KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal, 0);
+
+		/* First vsync will set cur_vtotal. */
+		KUNIT_EXPECT_TRUE(test, drm_crtc_helper_vrr_vsync(crtc_state));
+		KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+				crtc_state->vrr_state.base_vtotal);
+		/* First vsync with fixed rate will still be dynamic. */
+		KUNIT_EXPECT_TRUE(test, crtc_state->vrr_state.dynamic);
+	}
+
+	/* Try a second commit to evaluate the dynamic flag */
+	scoped_guard(modeset_lock, &ctx) {
+		allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry_2:
+		prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+		ret = drm_atomic_commit(state);
+		if (ret == -EDEADLK) {
+			drm_atomic_commit_clear(state);
+			ret = drm_modeset_backoff(&ctx);
+			if (!ret)
+				goto retry_2;
+		}
+		KUNIT_ASSERT_EQ(test, ret, 0);
+
+		crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+		KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+		KUNIT_EXPECT_FALSE(test, drm_crtc_helper_vrr_flush(crtc_state));
+		KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+				crtc_state->vrr_state.base_vtotal);
+		KUNIT_EXPECT_FALSE(test, crtc_state->vrr_state.dynamic);
+		KUNIT_EXPECT_FALSE(test, drm_crtc_helper_vrr_vsync(crtc_state));
+		KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.cur_vtotal,
+				crtc_state->vrr_state.base_vtotal);
+		/* Subsequent vsyncs with fixed rate won't be dynamic. */
+		KUNIT_EXPECT_FALSE(test, crtc_state->vrr_state.dynamic);
+	}
+}
+
+struct vrr_target_error_params {
+	unsigned int vrr_min_n;
+	unsigned int vrr_min_d;
+	unsigned int vrr_max_n;
+	unsigned int vrr_max_d;
+	bool cinema_vrr_override;
+	const char *desc;
+};
+
+static struct vrr_target_error_params vrr_target_errors[] = {
+	{ 26, 1, 26 * 2000, 2000, false, "VRR target <= min rate should fail" },
+	{ 200, 1, 200, 1, true, "VRR target <= max rate should fail" },
+	{ 60, 1, 40, 1, true, "VRR max target <= VRR min target should fail" },
+	{ 40, 1, 60, 0, true, "VRR max target denominator of 0 should fail" },
+	{ 40, 0, 60, 1, true, "VRR min target denominator of 0 should fail" },
+	{ 25000, 1001, 30, 1, true, "CinemaVRR with non-fixed rate should fail" },
+	{ 0, 1000, 60, 1, true, "Minimum-Incomplete range should fail" },
+	{ 40, 1, 0, 0, false, "Maximum-Incomplete range should fail" },
+};
+
+KUNIT_ARRAY_PARAM_DESC(check_vrr_target_errors, vrr_target_errors, desc);
+
+/*
+ * Check that on a VRR-capable sink with VRR enabled and various erroneous
+ * combinations of target rates set, an atomic commit fails.
+ */
+static void drm_test_check_hdmi_vrr_target_rate_fail(struct kunit *test)
+{
+	const struct vrr_target_error_params *params = test->param_value;
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_LT(test, 24, conn->display_info.hdmi.vrr_cap.vrr_min);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	/*
+	 * Allow individual test parameters to play with the CinemaVRR flag
+	 * without needing to bring their own modified EDID.
+	 */
+	conn->display_info.hdmi.vrr_cap.cinema_vrr = params->cinema_vrr_override;
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = params->vrr_min_n;
+	crtc_state->vrr_state.vrr_min_d = params->vrr_min_d;
+	crtc_state->vrr_state.vrr_max_n = params->vrr_max_n;
+	crtc_state->vrr_state.vrr_max_d = params->vrr_max_d;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Check that on a VRR-capable sink that has the CinemaVRR flag set, with VRR
+ * enabled and a fixed target rate set, an atomic commit succeeds at a rate
+ * only possible if CinemaVRR is supported and a fixed target is used.
+ */
+static void drm_test_check_hdmi_vrr_fixed_rate_cinema_vrr(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int mode_refresh;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.cinema_vrr);
+	KUNIT_ASSERT_LT(test, 24, conn->display_info.hdmi.vrr_cap.vrr_min);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+
+	/* 24/1.001 Hz should work */
+	crtc_state->vrr_state.vrr_min_n = 24000;
+	crtc_state->vrr_state.vrr_min_d = 1001;
+	crtc_state->vrr_state.vrr_max_n = 24000;
+	crtc_state->vrr_state.vrr_max_d = 1001;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.base_vtotal,
+			mult_frac(preferred->crtc_vtotal, mode_refresh * 1000, 23976));
+}
+
+/*
+ * Check that on a VRR-capable sink, an atomic commit with a VRR minimum and
+ * maximum target rate that's more narrow than the monitor's target rate
+ * succeeds and produces the expected derived values.
+ */
+static void drm_test_check_hdmi_vrr_constrained_rate(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int mode_refresh;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 40;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	crtc_state->vrr_state.vrr_max_n = 80;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, priv->crtc);
+	KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.base_vtotal,
+			preferred->crtc_vtotal * mode_refresh / 80);
+	KUNIT_EXPECT_EQ(test, crtc_state->vrr_state.max_vtotal,
+			preferred->crtc_vtotal * mode_refresh / 40);
+}
+
 static struct kunit_case drm_atomic_helper_connector_hdmi_vrr_tests[] = {
 	KUNIT_CASE(drm_test_check_hdmi_vrr),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_sink_fail),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_flush_vsync),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_fixed_rate),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_fixed_rate_flush_vsync),
+	KUNIT_CASE_PARAM(drm_test_check_hdmi_vrr_target_rate_fail,
+			 check_vrr_target_errors_gen_params),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_fixed_rate_cinema_vrr),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_constrained_rate),
 	{ }
 };
 

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 24/27] drm/tests: hdmi: Add Quick Media Switching tests
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (22 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 23/27] drm/tests: hdmi: Add Fixed/Constrained rate " Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 25/27] drm/connector: Fix docs for cnmvrr and cinemavrr members Nicolas Frattaroli
                   ` (2 subsequent siblings)
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Add KUnit tests to validate the HDMI Quick Media Switching (QMS)
functionality in the HDMI state helpers.

This includes tests to validate that the QMS TFR min and QMS TFR max
flags are respected.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 574 ++++++++++++++++++++-
 drivers/gpu/drm/tests/drm_kunit_edid.c             | 280 ++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.h             |   2 +
 3 files changed, 855 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index 4d9bcab57778..4c7e563ca51d 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -303,8 +303,10 @@ __connector_hdmi_init(struct kunit *test,
 
 	ret = drm_connector_attach_vrr_display_mode_property(conn,
 							     BIT(VRR_DISPLAY_MODE_GAME) |
-							     BIT(VRR_DISPLAY_MODE_OFF));
+							     BIT(VRR_DISPLAY_MODE_OFF) |
+							     BIT(VRR_DISPLAY_MODE_MEDIA));
 	KUNIT_ASSERT_EQ(test, ret, 0);
+	conn->hdmi.qms_supported = true;
 
 	drm_mode_config_reset(drm);
 
@@ -4054,6 +4056,568 @@ static void drm_test_check_hdmi_vrr_constrained_rate(struct kunit *test)
 			preferred->crtc_vtotal * mode_refresh / 40);
 }
 
+/*
+ * Check that on a QMS-capable sink, an atomic commit with VRR display mode
+ * set to "Media" succeeds, and produces the right connector state values.
+ */
+static void drm_test_check_hdmi_vrr_qms(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 48000;
+	crtc_state->vrr_state.vrr_min_d = 1001;
+	/* Equivalent fraction, as an added hurdle */
+	crtc_state->vrr_state.vrr_max_n = 48000 * 2;
+	crtc_state->vrr_state.vrr_max_d = 1001 * 2;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	conn_state = drm_atomic_get_new_connector_state(state, conn);
+	KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+	KUNIT_EXPECT_EQ(test, conn_state->hdmi.qms_tfr_byte, 6);
+}
+
+/*
+ * Check that on a QMS-capable sink, an atomic commit with VRR display mode
+ * set to "Media" fails if the requested target rate is not supported by QMS.
+ */
+static void drm_test_check_hdmi_vrr_qms_fail(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 67;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	crtc_state->vrr_state.vrr_max_n = 67;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+}
+
+/*
+ * Check that on a QMS-capable sink, an atomic commit with the VRR display mode
+ * set to "Media" fails if the requested target rate is not a fixed rate.
+ */
+static void drm_test_check_hdmi_vrr_qms_nonfixed_fail(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 30;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	crtc_state->vrr_state.vrr_max_n = 60;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+}
+
+/*
+ * Check that on a QMS-capable sink with QMS TFR min set, an atomic commit with
+ * the VRR display mode set to "Media" succeeds even if the target rate is
+ * below the monitor's range, and that it produces the right connector state
+ * values.
+ */
+static void drm_test_check_hdmi_vrr_qms_tfr_min(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_tfr_min);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+	/*
+	 * The CinemaVRR flag provides equivalent functionality to QMS Min TFR
+	 * for non-QMS VRR, so it should be false to ensure that this unit test
+	 * tests the right thing.
+	 */
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_cap.cinema_vrr);
+
+	KUNIT_ASSERT_LT(test, 24, conn->display_info.hdmi.vrr_cap.vrr_min);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 24;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	crtc_state->vrr_state.vrr_max_n = 24;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	conn_state = drm_atomic_get_new_connector_state(state, conn);
+	KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+	KUNIT_EXPECT_EQ(test, conn_state->hdmi.qms_tfr_byte, 2);
+}
+
+/*
+ * Check that on a QMS-capable sink without the QMS max TFR flag, an atomic
+ * commit with QMS enabled and a target rate above 60Hz fails.
+ */
+static void drm_test_check_hdmi_vrr_qms_no_tfr_max_fail(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_cap.qms_tfr_max);
+
+	KUNIT_ASSERT_LE(test, 100, conn->display_info.hdmi.vrr_cap.vrr_max);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 100;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	crtc_state->vrr_state.vrr_max_n = 100;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+}
+
+/*
+ * Check that on a QMS-capable sink with the QMS max TFR flag, an atomic commit
+ * with QMS enabled and a target rate above 60Hz succeeds.
+ */
+static void drm_test_check_hdmi_vrr_qms_tfr_max(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+
+	KUNIT_ASSERT_LE(test, 100, conn->display_info.hdmi.vrr_cap.vrr_max);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	/* Manual override to not have to define an additional EDID. */
+	conn->display_info.hdmi.vrr_cap.qms_tfr_max = true;
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 1000; /* Intentional tomfoolery. */
+	crtc_state->vrr_state.vrr_min_d = 10;
+	crtc_state->vrr_state.vrr_max_n = 100;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	conn_state = drm_atomic_get_new_connector_state(state, conn);
+	KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+	KUNIT_EXPECT_EQ(test, conn_state->hdmi.qms_tfr_byte, 11);
+}
+
+/*
+ * Check that on a QMS-capable but VRR-incapable sink, an atomic commit with VRR
+ * display mode set to "Media" succeeds, and produces the right connector state
+ * values.
+ */
+static void drm_test_check_hdmi_vrr_qms_only(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_qms_only);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 48000;
+	crtc_state->vrr_state.vrr_min_d = 1001;
+	/* Equivalent fraction, as an added hurdle */
+	crtc_state->vrr_state.vrr_max_n = 48000 * 2;
+	crtc_state->vrr_state.vrr_max_d = 1001 * 2;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_MEDIA;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	conn_state = drm_atomic_get_new_connector_state(state, conn);
+	KUNIT_ASSERT_NOT_NULL(test, conn_state);
+
+	KUNIT_EXPECT_EQ(test, conn_state->hdmi.qms_tfr_byte, 6);
+}
+
+/*
+ * Check that on a QMS-capable but VRR-incapable sink, an atomic commit with VRR
+ * display mode set to "Game" fails.
+ */
+static void drm_test_check_hdmi_vrr_qms_only_fail(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_connector_state *conn_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_display_mode *preferred;
+	struct drm_crtc_state *crtc_state;
+	struct drm_atomic_commit *state;
+	struct drm_connector *conn;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_qms_only);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_cap.supported);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->hdmi.qms_supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	guard(modeset_lock)(&ctx);
+
+	allocate_state_for_vrr_test(test, priv, &ctx, preferred, &state);
+
+retry:
+	prepare_state_for_vrr_test(test, priv, &ctx, state, &crtc_state);
+
+	crtc_state->vrr_enabled = true;
+	crtc_state->vrr_state.vrr_min_n = 48;
+	crtc_state->vrr_state.vrr_min_d = 1;
+	crtc_state->vrr_state.vrr_max_n = 48;
+	crtc_state->vrr_state.vrr_max_d = 1;
+
+	conn_state = drm_atomic_get_connector_state(state, conn);
+	if (PTR_ERR(conn_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->vrr_display_mode = VRR_DISPLAY_MODE_GAME;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry;
+	}
+	KUNIT_EXPECT_EQ(test, ret, -EOPNOTSUPP);
+}
+
 static struct kunit_case drm_atomic_helper_connector_hdmi_vrr_tests[] = {
 	KUNIT_CASE(drm_test_check_hdmi_vrr),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_sink_fail),
@@ -4064,6 +4628,14 @@ static struct kunit_case drm_atomic_helper_connector_hdmi_vrr_tests[] = {
 			 check_vrr_target_errors_gen_params),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_fixed_rate_cinema_vrr),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_constrained_rate),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_fail),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_nonfixed_fail),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_tfr_min),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_no_tfr_max_fail),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_tfr_max),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_only),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_qms_only_fail),
 	{ }
 };
 
diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.c b/drivers/gpu/drm/tests/drm_kunit_edid.c
index 8b78a8bf063d..03eb809909f0 100644
--- a/drivers/gpu/drm/tests/drm_kunit_edid.c
+++ b/drivers/gpu/drm/tests/drm_kunit_edid.c
@@ -1130,6 +1130,286 @@ const unsigned char test_edid_hdmi_vrr[] = {
 };
 EXPORT_SYMBOL(test_edid_hdmi_vrr);
 
+/*
+ * Max resolution: 3840x2160@60Hz with YUV420
+ * Max BPC:        16 for all modes
+ * Max TMDS clock: <340MHz, so set to 0
+ * VRR range: 30Hz to 120Hz
+ * CinemaVRR flag is unset
+ * QMS flag is set
+ * QMS TFRmin flag is set
+ *
+ * edid-decode (hex):
+ *
+ * 00 ff ff ff ff ff ff 00 31 d8 45 00 00 00 00 00
+ * 01 24 01 03 80 60 36 78 0f ee 91 a3 54 4c 99 26
+ * 0f 50 54 20 00 00 01 01 01 01 01 01 01 01 01 01
+ * 01 01 01 01 01 01 04 74 80 18 71 38 2d 40 58 2c
+ * 45 00 c0 1c 32 00 00 1e 04 74 00 30 f2 70 5a 80
+ * b0 58 8a 00 c0 1c 32 00 00 1e 00 00 00 fc 00 54
+ * 65 73 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd
+ * 00 18 78 18 87 22 00 0a 20 20 20 20 20 20 01 7c
+ *
+ * 02 03 29 31 42 3f 5f 6d 03 0c 00 10 00 78 00 20
+ * 00 00 00 20 61 6d d8 5d c4 01 00 80 07 40 1e 78
+ * 10 00 00 e2 0e 61 e2 00 ed 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28
+ *
+ * ----------------
+ *
+ * Block 0, Base EDID:
+ *  EDID Structure Version & Revision: 1.3
+ *  Vendor & Product Identification:
+ *    Manufacturer: LNX
+ *    Model: 69
+ *    Made in: week 1 of 2026
+ *  Basic Display Parameters & Features:
+ *    Digital display
+ *    Maximum image size: 96 cm x 54 cm
+ *    Gamma: 2.20
+ *    RGB color display
+ *    Default (sRGB) color space is primary color space
+ *    First detailed timing is the preferred timing
+ *    Supports GTF timings within operating range
+ *  Color Characteristics:
+ *    Red  : 0.6396, 0.3300
+ *    Green: 0.2998, 0.5996
+ *    Blue : 0.1503, 0.0595
+ *    White: 0.3125, 0.3291
+ *  Established Timings I & II:
+ *    DMT 0x04:   640x480    59.940476 Hz   4:3     31.469 kHz     25.175000 MHz
+ *  Standard Timings: none
+ *  Detailed Timing Descriptors:
+ *    DTD 1:  1920x1080  120.000000 Hz  16:9    135.000 kHz    297.000000 MHz (960 mm x 540 mm)
+ *                 Hfront   88 Hsync  44 Hback  148 Hpol P
+ *                 Vfront    4 Vsync   5 Vback   36 Vpol P
+ *    DTD 2:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz (960 mm x 540 mm)
+ *                 Hfront  176 Hsync  88 Hback  296 Hpol P
+ *                 Vfront    8 Vsync  10 Vback   72 Vpol P
+ *    Display Product Name: 'Test EDID'
+ *    Display Range Limits:
+ *      Monitor ranges (GTF): 24-120 Hz V, 24-135 kHz H, max dotclock 340 MHz
+ *  Extension blocks: 1
+ * Checksum: 0x7c
+ *
+ * ----------------
+ *
+ * Block 1, CTA-861 Extension Block:
+ *  Revision: 3
+ *  Supports YCbCr 4:4:4
+ *  Supports YCbCr 4:2:2
+ *  Native detailed modes: 1
+ *  Video Data Block:
+ *    VIC  63:  1920x1080  120.000000 Hz  16:9    135.000 kHz    297.000000 MHz
+ *    VIC  95:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz
+ *  Vendor-Specific Data Block (HDMI), OUI 00-0C-03:
+ *    Source physical address: 1.0.0.0
+ *    DC_48bit
+ *    DC_36bit
+ *    DC_30bit
+ *    DC_Y444
+ *    Maximum TMDS clock: 0 MHz
+ *    Extended HDMI video details:
+ *  Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8:
+ *    Version: 1
+ *    SCDC Present
+ *    Supports 16-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports 12-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports 10-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports QMS
+ *    VRRmin: 30 Hz
+ *    VRRmax: 120 Hz
+ *    Supports QMS TFRmin
+ *  YCbCr 4:2:0 Video Data Block:
+ *    VIC  97:  3840x2160   60.000000 Hz  16:9    135.000 kHz    594.000000 MHz
+ *  Video Capability Data Block:
+ *    YCbCr quantization: Selectable (via AVI YQ)
+ *    RGB quantization: Selectable (via AVI Q)
+ *    PT scan behavior: Always Underscanned
+ *    IT scan behavior: Supports both over- and underscan
+ *    CE scan behavior: Always Overscanned
+ * Checksum: 0x28  Unused space in Extension Block: 86 bytes
+ *
+ * ----------------
+ *
+ * edid-decode 1.32.0
+ *
+ * Warnings:
+ *
+ * Block 1, CTA-861 Extension Block:
+ *  IT Video Formats are overscanned by default, but normally this should be underscanned.
+ *
+ * EDID conformity: PASS
+ */
+const unsigned char test_edid_hdmi_vrr_qms[] = {
+	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x45, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0x24, 0x01, 0x03, 0x80, 0x60, 0x36, 0x78,
+	0x0f, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x20,
+	0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x74, 0x80, 0x18, 0x71, 0x38,
+	0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e,
+	0x04, 0x74, 0x00, 0x30, 0xf2, 0x70, 0x5a, 0x80, 0xb0, 0x58, 0x8a, 0x00,
+	0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54,
+	0x65, 0x73, 0x74, 0x20, 0x45, 0x44, 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20,
+	0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, 0x78, 0x18, 0x87, 0x22, 0x00, 0x0a,
+	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x7c, 0x02, 0x03, 0x29, 0x31,
+	0x42, 0x3f, 0x5f, 0x6d, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x78, 0x00, 0x20,
+	0x00, 0x00, 0x00, 0x20, 0x61, 0x6d, 0xd8, 0x5d, 0xc4, 0x01, 0x00, 0x80,
+	0x07, 0x40, 0x1e, 0x78, 0x10, 0x00, 0x00, 0xe2, 0x0e, 0x61, 0xe2, 0x00,
+	0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x28
+};
+EXPORT_SYMBOL(test_edid_hdmi_vrr_qms);
+
+/*
+ * Max resolution: 3840x2160@60Hz with YUV420
+ * Max BPC:        16 for all modes
+ * Max TMDS clock: <340MHz, so set to 0
+ * VRR range: none
+ * CinemaVRR flag is unset
+ * QMS flag is set
+ * QMS TFRmin flag is set
+ *
+ * edid-decode (hex):
+ *
+ * 00 ff ff ff ff ff ff 00 31 d8 45 00 00 00 00 00
+ * 01 24 01 03 80 60 36 78 0f ee 91 a3 54 4c 99 26
+ * 0f 50 54 20 00 00 01 01 01 01 01 01 01 01 01 01
+ * 01 01 01 01 01 01 04 74 80 18 71 38 2d 40 58 2c
+ * 45 00 c0 1c 32 00 00 1e 04 74 00 30 f2 70 5a 80
+ * b0 58 8a 00 c0 1c 32 00 00 1e 00 00 00 fc 00 54
+ * 65 73 74 20 45 44 49 44 0a 20 20 20 00 00 00 fd
+ * 00 18 78 18 87 22 00 0a 20 20 20 20 20 20 01 7c
+ *
+ * 02 03 29 31 42 3f 5f 6d 03 0c 00 10 00 78 00 20
+ * 00 00 00 20 61 6d d8 5d c4 01 00 80 07 40 00 00
+ * 10 00 00 e2 0e 61 e2 00 ed 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 be
+ *
+ * ----------------
+ *
+ * Block 0, Base EDID:
+ *  EDID Structure Version & Revision: 1.3
+ *  Vendor & Product Identification:
+ *    Manufacturer: LNX
+ *    Model: 69
+ *    Made in: week 1 of 2026
+ *  Basic Display Parameters & Features:
+ *    Digital display
+ *    Maximum image size: 96 cm x 54 cm
+ *    Gamma: 2.20
+ *    RGB color display
+ *    Default (sRGB) color space is primary color space
+ *    First detailed timing is the preferred timing
+ *    Supports GTF timings within operating range
+ *  Color Characteristics:
+ *    Red  : 0.6396, 0.3300
+ *    Green: 0.2998, 0.5996
+ *    Blue : 0.1503, 0.0595
+ *    White: 0.3125, 0.3291
+ *  Established Timings I & II:
+ *    DMT 0x04:   640x480    59.940476 Hz   4:3     31.469 kHz     25.175000 MHz
+ *  Standard Timings: none
+ *  Detailed Timing Descriptors:
+ *    DTD 1:  1920x1080  120.000000 Hz  16:9    135.000 kHz    297.000000 MHz (960 mm x 540 mm)
+ *                 Hfront   88 Hsync  44 Hback  148 Hpol P
+ *                 Vfront    4 Vsync   5 Vback   36 Vpol P
+ *    DTD 2:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz (960 mm x 540 mm)
+ *                 Hfront  176 Hsync  88 Hback  296 Hpol P
+ *                 Vfront    8 Vsync  10 Vback   72 Vpol P
+ *    Display Product Name: 'Test EDID'
+ *    Display Range Limits:
+ *      Monitor ranges (GTF): 24-120 Hz V, 24-135 kHz H, max dotclock 340 MHz
+ *  Extension blocks: 1
+ * Checksum: 0x7c
+ *
+ * ----------------
+ *
+ * Block 1, CTA-861 Extension Block:
+ *  Revision: 3
+ *  Supports YCbCr 4:4:4
+ *  Supports YCbCr 4:2:2
+ *  Native detailed modes: 1
+ *  Video Data Block:
+ *    VIC  63:  1920x1080  120.000000 Hz  16:9    135.000 kHz    297.000000 MHz
+ *    VIC  95:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz
+ *  Vendor-Specific Data Block (HDMI), OUI 00-0C-03:
+ *    Source physical address: 1.0.0.0
+ *    DC_48bit
+ *    DC_36bit
+ *    DC_30bit
+ *    DC_Y444
+ *    Maximum TMDS clock: 0 MHz
+ *    Extended HDMI video details:
+ *  Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8:
+ *    Version: 1
+ *    SCDC Present
+ *    Supports 16-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports 12-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports 10-bits/component Deep Color 4:2:0 Pixel Encoding
+ *    Supports QMS
+ *    Supports QMS TFRmin
+ *  YCbCr 4:2:0 Video Data Block:
+ *    VIC  97:  3840x2160   60.000000 Hz  16:9    135.000 kHz    594.000000 MHz
+ *  Video Capability Data Block:
+ *    YCbCr quantization: Selectable (via AVI YQ)
+ *    RGB quantization: Selectable (via AVI Q)
+ *    PT scan behavior: Always Underscanned
+ *    IT scan behavior: Supports both over- and underscan
+ *    CE scan behavior: Always Overscanned
+ * Checksum: 0xbe  Unused space in Extension Block: 86 bytes
+ *
+ * ----------------
+ *
+ * edid-decode 1.32.0
+ *
+ * Warnings:
+ *
+ * Block 1, CTA-861 Extension Block:
+ *  IT Video Formats are overscanned by default, but normally this should be underscanned.
+ *
+ * EDID conformity: PASS
+ */
+const unsigned char test_edid_hdmi_qms_only[] = {
+	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x45, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0x24, 0x01, 0x03, 0x80, 0x60, 0x36, 0x78,
+	0x0f, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x20,
+	0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x74, 0x80, 0x18, 0x71, 0x38,
+	0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e,
+	0x04, 0x74, 0x00, 0x30, 0xf2, 0x70, 0x5a, 0x80, 0xb0, 0x58, 0x8a, 0x00,
+	0xc0, 0x1c, 0x32, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54,
+	0x65, 0x73, 0x74, 0x20, 0x45, 0x44, 0x49, 0x44, 0x0a, 0x20, 0x20, 0x20,
+	0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, 0x78, 0x18, 0x87, 0x22, 0x00, 0x0a,
+	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x7c, 0x02, 0x03, 0x29, 0x31,
+	0x42, 0x3f, 0x5f, 0x6d, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x78, 0x00, 0x20,
+	0x00, 0x00, 0x00, 0x20, 0x61, 0x6d, 0xd8, 0x5d, 0xc4, 0x01, 0x00, 0x80,
+	0x07, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0xe2, 0x0e, 0x61, 0xe2, 0x00,
+	0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0xbe
+};
+EXPORT_SYMBOL(test_edid_hdmi_qms_only);
+
 MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
 MODULE_DESCRIPTION("EDID Definitions for KUnit tests");
 MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.h b/drivers/gpu/drm/tests/drm_kunit_edid.h
index 80dc15a8bcb6..e993bbd5427b 100644
--- a/drivers/gpu/drm/tests/drm_kunit_edid.h
+++ b/drivers/gpu/drm/tests/drm_kunit_edid.h
@@ -13,5 +13,7 @@ extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz[256];
 extern const unsigned char test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz[256];
 extern const unsigned char test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz[256];
 extern const unsigned char test_edid_hdmi_vrr[256];
+extern const unsigned char test_edid_hdmi_vrr_qms[256];
+extern const unsigned char test_edid_hdmi_qms_only[256];
 
 #endif // DRM_KUNIT_EDID_H_

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 25/27] drm/connector: Fix docs for cnmvrr and cinemavrr members
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (23 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 24/27] drm/tests: hdmi: Add Quick Media Switching tests Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 26/27] drm/debugfs: Expose HDMI VRR in vrr_range debugfs Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 27/27] drm/tests: hdmi: Add tests for vrr_display_{min,max} properties Nicolas Frattaroli
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

The member names in the doc comments are inexplicably prefixed with an
"m", which does not reflect the actual struct member's name.

Drop the "m".

Fixes: 014da1db5e64 ("drm/edid: parse HDMI 2.1 gaming (ALLM/VRR) capabilities from HF-VSDB")
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 include/drm/drm_connector.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 41f272afbb1e..021ab07a1d9b 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -265,10 +265,10 @@ struct drm_hdmi_vrr_cap {
 	/** @fva: flag for Fast VActive (Quick Frame Transport) support */
 	bool fva;
 
-	/** @mcnmvrr: flag for Negative M VRR support */
+	/** @cnmvrr: flag for Negative M VRR support */
 	bool cnmvrr;
 
-	/** @mcinema_vrr: flag for Cinema VRR support */
+	/** @cinema_vrr: flag for Cinema VRR support */
 	bool cinema_vrr;
 
 	/** @mdelta: flag for limited frame-to-frame compensation support */

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 26/27] drm/debugfs: Expose HDMI VRR in vrr_range debugfs
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (24 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 25/27] drm/connector: Fix docs for cnmvrr and cinemavrr members Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  2026-11-05 15:52 ` [PATCH v2 27/27] drm/tests: hdmi: Add tests for vrr_display_{min,max} properties Nicolas Frattaroli
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

IGT requires the vrr_range debugfs file for running the tests, for
better or worse.

Since Commit 014da1db5e64 ("drm/edid: parse HDMI 2.1 gaming (ALLM/VRR)
capabilities from HF-VSDB") parses the HDMI VRR minimum and maximum
numbers into separate members and not the display_info.monitor_range
member, this debugfs entry needs to use those members when the display
is HDMI and does HDMI VRR.

Add this special casing, and also protect accesses to connector->status
and connector->display_info interruptibly with the mode_config mutex.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/drm_debugfs.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c
index a72f9723d3a9..476f44642ac3 100644
--- a/drivers/gpu/drm/drm_debugfs.c
+++ b/drivers/gpu/drm/drm_debugfs.c
@@ -586,12 +586,26 @@ static ssize_t edid_write(struct file *file, const char __user *ubuf,
 static int vrr_range_show(struct seq_file *m, void *data)
 {
 	struct drm_connector *connector = m->private;
+	u16 min_vfreq, max_vfreq;
+
+	scoped_cond_guard(mutex_intr, return -EINTR,
+			  &connector->dev->mode_config.mutex) {
+		struct drm_display_info *info = &connector->display_info;
+
+		if (connector->status != connector_status_connected)
+			return -ENODEV;
+
+		if (info->is_hdmi && info->hdmi.vrr_cap.supported) {
+			min_vfreq = info->hdmi.vrr_cap.vrr_min;
+			max_vfreq = info->hdmi.vrr_cap.vrr_max;
+		} else {
+			min_vfreq = info->monitor_range.min_vfreq;
+			max_vfreq = info->monitor_range.max_vfreq;
+		}
+	}
 
-	if (connector->status != connector_status_connected)
-		return -ENODEV;
-
-	seq_printf(m, "Min: %u\n", connector->display_info.monitor_range.min_vfreq);
-	seq_printf(m, "Max: %u\n", connector->display_info.monitor_range.max_vfreq);
+	seq_printf(m, "Min: %u\n", min_vfreq);
+	seq_printf(m, "Max: %u\n", max_vfreq);
 
 	return 0;
 }

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v2 27/27] drm/tests: hdmi: Add tests for vrr_display_{min,max} properties
  2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (25 preceding siblings ...)
  2026-11-05 15:52 ` [PATCH v2 26/27] drm/debugfs: Expose HDMI VRR in vrr_range debugfs Nicolas Frattaroli
@ 2026-11-05 15:52 ` Nicolas Frattaroli
  26 siblings, 0 replies; 28+ messages in thread
From: Nicolas Frattaroli @ 2026-11-05 15:52 UTC (permalink / raw)
  To: Xaver Hugl, Vidith Madhu, Borah, Chaitanya Kumar, Leo Li,
	Harry Wentland, Fangzhi Zuo, Daniel Stone, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Helge Deller, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Sandy Huang, Heiko Stübner, Andy Yan, Michel Dänzer
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Attach the vrr_display_min and vrr_display_max properties to the KUnit
HDMI state helper test connector, and add two simple tests to verify the
introduced drm_connector_hdmi_update_vrr_limits() helper sets the
properties right depending on EDID.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 87 ++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index 4c7e563ca51d..7bd06b65d241 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -308,6 +308,9 @@ __connector_hdmi_init(struct kunit *test,
 	KUNIT_ASSERT_EQ(test, ret, 0);
 	conn->hdmi.qms_supported = true;
 
+	ret = drm_connector_attach_vrr_display_limits_properties(conn);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
 	drm_mode_config_reset(drm);
 
 	if (edid_data && edid_len) {
@@ -3676,6 +3679,88 @@ static void drm_test_check_hdmi_vrr_flush_vsync(struct kunit *test)
 			crtc_state->vrr_state.max_vtotal);
 }
 
+/*
+ * Check that on a VRR-capable sink, the HDMI VRR limits helper sets the
+ * display_min/display_max connector properties to the right values.
+ */
+static void drm_test_check_hdmi_vrr_display_limits(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_display_mode *preferred;
+	struct drm_connector *conn;
+	u64 disp_min, disp_max;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_vrr);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	ret = drm_connector_hdmi_update_vrr_limits(conn, preferred);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	ret = drm_object_property_get_value(&conn->base, conn->vrr_display_min_property,
+					    &disp_min);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	ret = drm_object_property_get_value(&conn->base, conn->vrr_display_max_property,
+					    &disp_max);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	KUNIT_EXPECT_EQ(test, disp_min, 30);
+	KUNIT_EXPECT_EQ(test, disp_max, 120);
+}
+
+/*
+ * Check that on a non-VRR-capable sink, the HDMI VRR limits helper sets the
+ * display_min/display_max connector properties to 0.
+ */
+static void drm_test_check_hdmi_vrr_display_limits_zero(struct kunit *test)
+{
+	struct drm_atomic_helper_connector_hdmi_priv *priv;
+	struct drm_display_mode *preferred;
+	struct drm_connector *conn;
+	u64 disp_min, disp_max;
+	int ret;
+
+	priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test,
+				BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
+				8,
+				&dummy_connector_hdmi_funcs,
+				test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_cap.supported);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	ret = drm_connector_hdmi_update_vrr_limits(conn, preferred);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	ret = drm_object_property_get_value(&conn->base, conn->vrr_display_min_property,
+					    &disp_min);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	ret = drm_object_property_get_value(&conn->base, conn->vrr_display_max_property,
+					    &disp_max);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	KUNIT_EXPECT_EQ(test, disp_min, 0);
+	KUNIT_EXPECT_EQ(test, disp_max, 0);
+}
+
 /*
  * Check that on a VRR-capable sink with VRR enabled and a fixed target rate
  * set, an atomic commit works, and the derived vtotals are as expected.
@@ -4622,6 +4707,8 @@ static struct kunit_case drm_atomic_helper_connector_hdmi_vrr_tests[] = {
 	KUNIT_CASE(drm_test_check_hdmi_vrr),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_sink_fail),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_flush_vsync),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_display_limits),
+	KUNIT_CASE(drm_test_check_hdmi_vrr_display_limits_zero),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_fixed_rate),
 	KUNIT_CASE(drm_test_check_hdmi_vrr_fixed_rate_flush_vsync),
 	KUNIT_CASE_PARAM(drm_test_check_hdmi_vrr_target_rate_fail,

-- 
2.56.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2026-11-05 15:57 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 15:51 [PATCH v2 00/27] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 01/27] drm: Add VRR state Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 02/27] drm/connector: add vrr_display_mode connector property Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 03/27] video/hdmi: Add VTEM EMP packing Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 04/27] drm/bridge: Add VTEM EMP support Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 05/27] drm/connector: hdmi: Add VTEM EMP generation Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 06/27] drm/crtc-helper: Add VRR helper functions Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 07/27] drm/bridge: synopsys: Add VTEM EMP support Nicolas Frattaroli
2026-11-05 15:51 ` [PATCH v2 08/27] drm/connector: Add drm_display_info_is_vrr_capable Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 09/27] drm: Add VRR target frame rate properties Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 10/27] drm/connector: Add vrr_display_{min,max} connector properties Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 11/27] drm: Implement VRR rate limiting Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 12/27] drm/rockchip: dw_hdmi_qp: Add VRR support Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 13/27] drm/rockchip: Set mode_changed on vrr_enabled change Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 14/27] drm/rockchip: vop2: Enable VRR Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 15/27] drm/edid: Parse QMS flag from HDMI SCDS Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 16/27] drm/edid: Parse QMS TFR min/max flags " Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 17/27] drm/connector: Add "Media" VRR display mode for QMS Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 18/27] video/hdmi: Add support for QMS in VTEM EMP packing Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 19/27] drm/connector: hdmi: Add QMS to VTEM EMP generation Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 20/27] drm/connector: hdmi: Add QMS state validation and computation Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 21/27] drm/rockchip: dw_hdmi_qp: Add QMS support Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 22/27] drm/tests: hdmi: Add "Game Mode" VRR tests Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 23/27] drm/tests: hdmi: Add Fixed/Constrained rate " Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 24/27] drm/tests: hdmi: Add Quick Media Switching tests Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 25/27] drm/connector: Fix docs for cnmvrr and cinemavrr members Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 26/27] drm/debugfs: Expose HDMI VRR in vrr_range debugfs Nicolas Frattaroli
2026-11-05 15:52 ` [PATCH v2 27/27] drm/tests: hdmi: Add tests for vrr_display_{min,max} properties Nicolas Frattaroli

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®