mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation
@ 2026-09-21 15:51 Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 01/25] drm/edid: Add a query for vrr range Nicolas Frattaroli
                   ` (24 more replies)
  0 siblings, 25 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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).

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 first ~11 patches implement "game mode" VRR for the HDMI state
helpers, and wire it up for RK3588. For the people who care about the
uAPI, this part may not be super interesting.

The uAPI consists of 4 new CRTC properties for the frame rate
minimum/maximum target, and 1 new connector property for enabling QMS
signalling. The qms_enabled property is present when the connector
supports QMS signalling; Whether or not a *display* supports QMS
signalling is determined by the EDID, and userspace can do the necessary
EDID parsing by itself. (i.e., there is no qms_capable property that
exposes the kernel's own EDID parsing results to userspace.)

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

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
Derek Foreman (5):
      drm/edid: Add a query for vrr range
      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 (20):
      drm: Add VRR state
      drm/atomic-helper: Set mode_changed on vrr_enabled change
      drm/crtc-helper: Add VRR helper functions
      drm/connector: Add drm_display_info_is_vrr_capable
      drm/rockchip: dw_hdmi_qp: Add VRR support
      drm/rockchip: vop2: Enable VRR
      drm/edid: Parse CinemaVRR flag from HDMI SCDS
      drm: Add VRR target frame rate properties
      drm: Implement VRR rate limiting
      drm/edid: Parse QMS flag from HDMI SCDS
      drm/edid: Parse QMS TFR min/max flags from HDMI SCDS
      drm/connector: Add "qms_enabled" drm property
      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/atomic: Disable VRR in helper_set_config

 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_state_helper.c    |  474 +++++++
 drivers/gpu/drm/drm_atomic.c                       |    2 +
 drivers/gpu/drm/drm_atomic_helper.c                |    9 +
 drivers/gpu/drm/drm_atomic_uapi.c                  |   28 +
 drivers/gpu/drm/drm_connector.c                    |  112 ++
 drivers/gpu/drm/drm_crtc.c                         |    8 +
 drivers/gpu/drm/drm_crtc_helper.c                  |   93 ++
 drivers/gpu/drm/drm_edid.c                         |   38 +
 drivers/gpu/drm/drm_mode_config.c                  |   20 +
 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c     |   23 +
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.c       |   62 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.h       |    9 +
 drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 1383 ++++++++++++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.h             |  279 ++++
 drivers/video/hdmi.c                               |   99 +-
 include/drm/drm_bridge.h                           |   29 +
 include/drm/drm_connector.h                        |   59 +
 include/drm/drm_crtc.h                             |   18 +
 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 +
 25 files changed, 2898 insertions(+), 14 deletions(-)
---
base-commit: ab82471ddff094939d9981e0647332562c7e1c9b
change-id: 20260919-vrr-limiter-uapi-c99ed93fe7d2

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


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

* [PATCH RFC 01/25] drm/edid: Add a query for vrr range
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 02/25] drm: Add VRR state Nicolas Frattaroli
                   ` (23 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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>

Query the minimum and maximum VRR vertical rates from EDID.

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

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 3148038747b6..a1494028e0f5 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6309,6 +6309,25 @@ static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc,
 		hdmi_dsc->total_chunk_kbytes = hf_scds[13] & DRM_EDID_DSC_TOTAL_CHUNK_KBYTES;
 }
 
+static void drm_parse_vrr_info(struct drm_display_info *info, const u8 *hf_scds)
+{
+	struct drm_monitor_range_info *range = &info->monitor_range;
+	const int pld_len = cea_db_payload_len(hf_scds);
+
+	if (pld_len < 10)
+		return;
+
+	if (!range->min_vfreq)
+		range->min_vfreq = FIELD_GET(DRM_EDID_VRR_MIN_MASK, hf_scds[9]);
+
+	if (pld_len < 11)
+		return;
+
+	if (!range->max_vfreq)
+		range->max_vfreq = (FIELD_GET(DRM_EDID_VRR_MAX_UPPER_MASK, hf_scds[9]) << 8) |
+				    hf_scds[10];
+}
+
 /* Sink Capability Data Structure */
 static void drm_parse_hdmi_forum_scds(struct drm_connector *connector,
 				      const u8 *hf_scds)
@@ -6371,6 +6390,13 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector,
 		dsc_support = true;
 	}
 
+	drm_parse_vrr_info(info, hf_scds);
+
+	if (info->monitor_range.min_vfreq > 0)
+		hdmi->vrr_capable = true;
+	else
+		hdmi->vrr_capable = false;
+
 	drm_dbg_kms(connector->dev,
 		    "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n",
 		    connector->base.id, connector->name,
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 2ee7c59329aa..593fbeaffa16 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -380,6 +380,9 @@ struct drm_hdmi_info {
 
 	/** @dsc_cap: DSC capabilities of the sink */
 	struct drm_hdmi_dsc_cap dsc_cap;
+
+	/** @vrr_capable: The sink supports variable refresh rate */
+	bool vrr_capable;
 };
 
 /**

-- 
2.55.0


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

* [PATCH RFC 02/25] drm: Add VRR state
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 01/25] drm/edid: Add a query for vrr range Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change Nicolas Frattaroli
                   ` (22 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 227 ++++++++++++++++++++++++
 include/drm/drm_crtc.h                          |  11 ++
 2 files changed, 238 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index 7cdb7ca3dc12..d55548399687 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -887,6 +887,229 @@ 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_state *old_crtc_state =
+		drm_atomic_get_old_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 =
+		&old_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->monitor_range.min_vfreq;
+	u32 min_vfreq_d = 1;
+	u32 max_vfreq_n = info->monitor_range.max_vfreq;
+	u32 max_vfreq_d = 1;
+
+	/* Save on the expensive vic lookup, if nothing else. */
+	if (!new_crtc_state->mode_changed &&
+	    old_crtc_state->vrr_enabled &&
+	    old_vrr_state->vrr_min_n == vrr_state->vrr_min_n &&
+	    old_vrr_state->vrr_min_d == vrr_state->vrr_min_d &&
+	    old_vrr_state->vrr_max_n == vrr_state->vrr_max_n &&
+	    old_vrr_state->vrr_max_d == vrr_state->vrr_max_d &&
+	    info->hdmi.vrr_capable) {
+		memcpy(vrr_state, old_vrr_state, sizeof(*vrr_state));
+		vrr_state->dynamic = false;
+		return 0;
+	}
+
+	if (!new_crtc_state->vrr_enabled)
+		return 0;
+
+	if (!info->hdmi.vrr_capable)
+		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;
+	}
+
+	vrr_state->vic = drm_match_cea_mode(mode);
+
+	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 +1142,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..9d58158af459 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -63,6 +63,14 @@ struct drm_atomic_commit;
 struct drm_crtc_helper_funcs;
 struct drm_plane_helper_funcs;
 
+struct drm_crtc_vrr_state {
+	u8 vic;
+	u16 cur_vtotal;
+	u16 max_vtotal;
+	u16 base_vtotal;
+	bool dynamic;
+};
+
 /**
  * struct drm_crtc_state - mutable CRTC state
  *
@@ -397,6 +405,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.55.0


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

* [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 01/25] drm/edid: Add a query for vrr range Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 02/25] drm: Add VRR state Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 21:59   ` Leo Li
  2026-09-21 22:01   ` Leo Li
  2026-09-21 15:51 ` [PATCH RFC 04/25] video/hdmi: Add VTEM EMP packing Nicolas Frattaroli
                   ` (21 subsequent siblings)
  24 siblings, 2 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

A change in whether VRR is enabled or not on a CRTC will be of interest
to any VRR implementation, as they may need to recalculate certain
internal state in response. So far, this was handled ad-hoc in the
drivers that implement VRR.

Handle this in the common DRM atomic core by setting the mode_changed
flag when this occurs. The rationale for using mode_changed is that VRR
is deeply intertwined with the current mode with regards to its timing.
Consequently, any component that cares about a mode change is likely
also going to care about VRR being enabled/disabled.

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

diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 9d006f98413a..0664d5779282 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -694,6 +694,12 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
 			new_crtc_state->active_changed = true;
 		}
 
+		if (old_crtc_state->vrr_enabled != new_crtc_state->vrr_enabled) {
+			drm_dbg_atomic(dev, "[CRTC:%d:%s] VRR changed\n",
+				       crtc->base.id, crtc->name);
+			new_crtc_state->mode_changed = true;
+		}
+
 		if (new_crtc_state->enable != has_connectors) {
 			drm_dbg_atomic(dev, "[CRTC:%d:%s] enabled/connectors mismatch (%d/%d)\n",
 				       crtc->base.id, crtc->name,

-- 
2.55.0


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

* [PATCH RFC 04/25] video/hdmi: Add VTEM EMP packing
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (2 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 05/25] drm/bridge: Add VTEM EMP support Nicolas Frattaroli
                   ` (20 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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.55.0


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

* [PATCH RFC 05/25] drm/bridge: Add VTEM EMP support
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (3 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 04/25] video/hdmi: Add VTEM EMP packing Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 06/25] drm/connector: hdmi: Add VTEM EMP generation Nicolas Frattaroli
                   ` (19 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 707d7143e475..dc814dbb5120 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -610,6 +610,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)
 {
@@ -655,6 +682,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 =
@@ -946,6 +978,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);
 		}
 
@@ -1072,6 +1109,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 77b3ffe69230..9dbd1bfa39af 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 593fbeaffa16..e561a444515f 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1633,6 +1633,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.55.0


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

* [PATCH RFC 06/25] drm/connector: hdmi: Add VTEM EMP generation
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (4 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 05/25] drm/bridge: Add VTEM EMP support Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 07/25] drm/crtc-helper: Add VRR helper functions Nicolas Frattaroli
                   ` (18 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 60 +++++++++++++++++++++++++
 include/drm/drm_connector.h                     |  5 +++
 2 files changed, 65 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index d55548399687..33d0c9491643 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -853,6 +853,56 @@ 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;
+
+	infoframe->set = false;
+
+	if (!connector->hdmi.funcs->vtem.write_infoframe)
+		return 0;
+
+	if (!info->hdmi.vrr_capable)
+		return 0;
+
+	hdmi_emp_infoframe_vtem_init(vtem);
+	if (!crtc_state->vrr_enabled || vrr->vic) {
+		vtem->base_refresh_rate = 0;
+		vtem->base_vfront = 0;
+	} else {
+		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;
+	}
+	vtem->fva_factor_m1 = 0;
+	infoframe->set = true;
+
+	if (!crtc_state->vrr_enabled) {
+		vtem->m_const = false;
+		vtem->game_vrr_en = false;
+		return 0;
+	}
+
+	vtem->game_vrr_en = true;
+
+	vtem->m_const = !vrr->dynamic;
+
+	return 0;
+}
+
 static int
 hdmi_generate_infoframes(const struct drm_connector *connector,
 			 struct drm_connector_state *conn_state)
@@ -884,6 +934,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;
 }
 
@@ -1494,6 +1548,12 @@ int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *con
 			goto out;
 	}
 
+	if (info->hdmi.vrr_capable)
+		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 e561a444515f..6e431eb81705 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1180,6 +1180,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.55.0


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

* [PATCH RFC 07/25] drm/crtc-helper: Add VRR helper functions
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (5 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 06/25] drm/connector: hdmi: Add VTEM EMP generation Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 08/25] drm/bridge: synopsys: Add VTEM EMP support Nicolas Frattaroli
                   ` (17 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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.55.0


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

* [PATCH RFC 08/25] drm/bridge: synopsys: Add VTEM EMP support
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (6 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 07/25] drm/crtc-helper: Add VRR helper functions Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 09/25] drm/connector: Add drm_display_info_is_vrr_capable Nicolas Frattaroli
                   ` (16 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 c9568aea98dc..3315082d4475 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
@@ -673,6 +673,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;
@@ -775,6 +784,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)
 {
@@ -1000,6 +1045,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,
@@ -1118,7 +1165,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;
 	if (!hdmi->no_hpd)
 		hdmi->bridge.ops |= DRM_BRIDGE_OP_HPD;
 	hdmi->bridge.of_node = pdev->dev.of_node;
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.55.0


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

* [PATCH RFC 09/25] drm/connector: Add drm_display_info_is_vrr_capable
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (7 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 08/25] drm/bridge: synopsys: Add VTEM EMP support Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 10/25] drm/rockchip: dw_hdmi_qp: Add VRR support Nicolas Frattaroli
                   ` (15 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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_capable 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 d94c86bfed86..fc7d9fcf9d9f 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_capable;
+
+	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 6e431eb81705..b18684d039d5 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1072,6 +1072,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.55.0


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

* [PATCH RFC 10/25] drm/rockchip: dw_hdmi_qp: Add VRR support
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (8 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 09/25] drm/connector: Add drm_display_info_is_vrr_capable Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 11/25] drm/rockchip: vop2: Enable VRR Nicolas Frattaroli
                   ` (14 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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.

In the encoder's atomic_check callback, the vrr_capable property, if
present, is then set to true if the sink supports VRR.

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

diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
index 9c4cf68d79d5..2b9432aa8c54 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
@@ -192,6 +192,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 = {};
@@ -238,6 +239,10 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
 		dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret);
 	}
 
+	if (connector->vrr_capable_property)
+		drm_connector_set_vrr_capable_property(connector,
+			drm_display_info_is_vrr_capable(&connector->display_info));
+
 	return ret;
 }
 
@@ -515,6 +520,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 = {
@@ -534,6 +540,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[] = {
@@ -695,6 +702,13 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
 		return dev_err_probe(hdmi->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");
+	}
+
 	return 0;
 }
 

-- 
2.55.0


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

* [PATCH RFC 11/25] drm/rockchip: vop2: Enable VRR
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (9 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 10/25] drm/rockchip: dw_hdmi_qp: Add VRR support Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 12/25] drm/edid: Parse CinemaVRR flag from HDMI SCDS Nicolas Frattaroli
                   ` (13 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 62 ++++++++++++++++++++++------
 drivers/gpu/drm/rockchip/rockchip_drm_vop2.h |  9 ++++
 2 files changed, 59 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..e79081c04437 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;
 			}
 
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index ffcb39c130aa..fdb2030e3ad4 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -655,6 +655,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 +813,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;

-- 
2.55.0


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

* [PATCH RFC 12/25] drm/edid: Parse CinemaVRR flag from HDMI SCDS
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (10 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 11/25] drm/rockchip: vop2: Enable VRR Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 13/25] drm: Add VRR target frame rate properties Nicolas Frattaroli
                   ` (12 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

HDMI's extension to EDID contains a flag which is used by sinks to
signal that they support "CinemaVRR". When CinemaVRR is enabled, some
VRR is possible even if the minimum VRR range is set to 0.

Parse it from the SCDS and store it in the sink's drm_hdmi_info.

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

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index a1494028e0f5..cc6f2ba8c9f4 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6314,6 +6314,11 @@ static void drm_parse_vrr_info(struct drm_display_info *info, const u8 *hf_scds)
 	struct drm_monitor_range_info *range = &info->monitor_range;
 	const int pld_len = cea_db_payload_len(hf_scds);
 
+	if (pld_len < 9)
+		return;
+
+	info->hdmi.cinema_vrr = !!(hf_scds[8] & DRM_EDID_CINEMA_VRR);
+
 	if (pld_len < 10)
 		return;
 
@@ -6392,7 +6397,7 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector,
 
 	drm_parse_vrr_info(info, hf_scds);
 
-	if (info->monitor_range.min_vfreq > 0)
+	if (info->monitor_range.min_vfreq > 0 || hdmi->cinema_vrr)
 		hdmi->vrr_capable = true;
 	else
 		hdmi->vrr_capable = false;
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index b18684d039d5..b147b8474f1a 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -383,6 +383,9 @@ struct drm_hdmi_info {
 
 	/** @vrr_capable: The sink supports variable refresh rate */
 	bool vrr_capable;
+
+	/** @cinema_vrr: The sink supports "CinemaVRR" */
+	bool cinema_vrr;
 };
 
 /**

-- 
2.55.0


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

* [PATCH RFC 13/25] drm: Add VRR target frame rate properties
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (11 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 12/25] drm/edid: Parse CinemaVRR flag from HDMI SCDS Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 22:23   ` Leo Li
  2026-09-21 15:51 ` [PATCH RFC 14/25] drm: Implement VRR rate limiting Nicolas Frattaroli
                   ` (11 subsequent siblings)
  24 siblings, 1 reply; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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_uapi.c | 24 +++++++++++++++++++++
 drivers/gpu/drm/drm_connector.c   | 45 +++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_crtc.c        |  8 +++++++
 drivers/gpu/drm/drm_mode_config.c | 20 +++++++++++++++++
 include/drm/drm_crtc.h            |  4 ++++
 include/drm/drm_mode_config.h     | 45 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 146 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 1eebcf6f5e05..c9eb01d740b3 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 fc7d9fcf9d9f..5c07bd221977 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -2579,6 +2579,51 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *
  *	The driver may place further restrictions within these minimum
  *	and maximum bounds.
+ *
+ * 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.
+ *
+ * .. _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.
+ *	Otherwise, must also be 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. Otherwise, must also be zero.
  */
 
 /**
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 63ead8ba6756..9244fbf97029 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -322,6 +322,14 @@ static int __drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *
 					   config->prop_out_fence_ptr, 0);
 		drm_object_attach_property(&crtc->base,
 					   config->prop_vrr_enabled, 0);
+		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;
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 9d58158af459..df37f4ebb58e 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -67,6 +67,10 @@ struct drm_crtc_vrr_state {
 	u8 vic;
 	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;
 };
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.55.0


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

* [PATCH RFC 14/25] drm: Implement VRR rate limiting
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (12 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 13/25] drm: Add VRR target frame rate properties Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 15/25] drm/edid: Parse QMS flag from HDMI SCDS Nicolas Frattaroli
                   ` (10 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 108 ++++++++++++++++++++----
 drivers/gpu/drm/drm_crtc_helper.c               |  34 +++++++-
 include/drm/drm_crtc.h                          |   3 +
 include/drm/drm_crtc_helper.h                   |   2 +
 4 files changed, 130 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 33d0c9491643..7dbd678cf061 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>
@@ -1046,6 +1047,39 @@ 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;
+	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;
+		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;
+
+	if (vrr_state->base_vtotal < mode->crtc_vtotal)
+		return -EINVAL;
+
+	return 0;
+}
+
 static int hdmi_validate_vrr(struct drm_connector *connector,
 			     struct drm_atomic_commit *state)
 {
@@ -1067,6 +1101,7 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 	u32 min_vfreq_d = 1;
 	u32 max_vfreq_n = info->monitor_range.max_vfreq;
 	u32 max_vfreq_d = 1;
+	bool is_fixed;
 
 	/* Save on the expensive vic lookup, if nothing else. */
 	if (!new_crtc_state->mode_changed &&
@@ -1099,6 +1134,10 @@ 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.cinema_vrr)
+		min_vfreq_n = 24;
+
 	if (!min_vfreq_n) {
 		drm_dbg_kms(dev, "Could not get minimum VRR rate from sink\n");
 		return -EINVAL;
@@ -1112,25 +1151,64 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 
 	vrr_state->vic = drm_match_cea_mode(mode);
 
-	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..c46de778df30 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 * 2 >= vrr->denom) {
+			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 df37f4ebb58e..00e9df02f3d5 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -71,6 +71,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.55.0


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

* [PATCH RFC 15/25] drm/edid: Parse QMS flag from HDMI SCDS
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (13 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 14/25] drm: Implement VRR rate limiting Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 16/25] drm/edid: Parse QMS TFR min/max flags " Nicolas Frattaroli
                   ` (9 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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_info.

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 cc6f2ba8c9f4..5a7d3b7e65da 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6318,6 +6318,7 @@ static void drm_parse_vrr_info(struct drm_display_info *info, const u8 *hf_scds)
 		return;
 
 	info->hdmi.cinema_vrr = !!(hf_scds[8] & DRM_EDID_CINEMA_VRR);
+	info->hdmi.qms_capable = !!(hf_scds[8] & DRM_EDID_QMS);
 
 	if (pld_len < 10)
 		return;
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index b147b8474f1a..c97cf0625fda 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -386,6 +386,9 @@ struct drm_hdmi_info {
 
 	/** @cinema_vrr: The sink supports "CinemaVRR" */
 	bool cinema_vrr;
+
+	/** @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.55.0


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

* [PATCH RFC 16/25] drm/edid: Parse QMS TFR min/max flags from HDMI SCDS
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (14 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 15/25] drm/edid: Parse QMS flag from HDMI SCDS Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 17/25] drm/connector: Add "qms_enabled" drm property Nicolas Frattaroli
                   ` (8 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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_info 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 5a7d3b7e65da..4fe754328b1c 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6332,6 +6332,12 @@ static void drm_parse_vrr_info(struct drm_display_info *info, const u8 *hf_scds)
 	if (!range->max_vfreq)
 		range->max_vfreq = (FIELD_GET(DRM_EDID_VRR_MAX_UPPER_MASK, hf_scds[9]) << 8) |
 				    hf_scds[10];
+
+	if (pld_len < 12)
+		return;
+
+	info->hdmi.qms_tfr_min = !!(hf_scds[11] & DRM_EDID_QMS_TFR_MIN);
+	info->hdmi.qms_tfr_max = !!(hf_scds[11] & DRM_EDID_QMS_TFR_MAX);
 }
 
 /* Sink Capability Data Structure */
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index c97cf0625fda..8439c82db905 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -389,6 +389,18 @@ struct drm_hdmi_info {
 
 	/** @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.55.0


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

* [PATCH RFC 17/25] drm/connector: Add "qms_enabled" drm property
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (15 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 16/25] drm/edid: Parse QMS TFR min/max flags " Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 18/25] video/hdmi: Add support for QMS in VTEM EMP packing Nicolas Frattaroli
                   ` (7 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 DRM property to the connector to allow userspace to request the
kernel to use QMS. Only connectors of sources which do support QMS
should register this property, though whether a specific sink supports
QMS is orthogonal to this.

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

diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 0664d5779282..a2919e3975a8 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -749,6 +749,9 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
 			    new_connector_state->color_format)
 				new_crtc_state->connectors_changed = true;
 
+			if (old_connector_state->hdmi.qms_enabled !=
+			    new_connector_state->hdmi.qms_enabled)
+				new_crtc_state->connectors_changed = true;
 		}
 
 		if (funcs->atomic_check)
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index c9eb01d740b3..fe9516475451 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -993,6 +993,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->qms_enabled_property) {
+		state->hdmi.qms_enabled = val;
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1080,6 +1082,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->qms_enabled_property) {
+		*val = state->hdmi.qms_enabled;
 	} 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 5c07bd221977..7ea235a58781 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -2605,6 +2605,8 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *	numerator/denominator pair of a frame rate to set as the minimum VRR
  *	target rate. Set to 0 to disable.
  *
+ * .. _VRR-MIN-DENOMINATOR:
+ *
  * "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
@@ -2619,11 +2621,30 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
  *	numerator/denominator pair of a frame rate to set as the maximum VRR
  *	target rate. Set to 0 to disable.
  *
+ * .. _VRR-MAX-DENOMINATOR:
+ *
  * "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. Otherwise, must also be zero.
+ *
+ * "qms_enabled":
+ *	Optional &drm_connector boolean property that drivers should attach with
+ *	drm_connector_attach_qms_enabled_property() on connectors that support
+ *	HDMI Quick Media Switching (QMS). Present only depending on source
+ *	capability, with no regards to sink capability. Userspace may set it to
+ *	true if the variable refresh target rate that's expressed through
+ *	:ref:`VRR_MIN_NUMERATOR <VRR-MIN-NUMERATOR>` divided by
+ *	:ref:`VRR_MIN_DENOMINATOR <VRR-MIN-DENOMINATOR>` (which, in this case,
+ *	should be equal to :ref:`VRR_MAX_NUMERATOR <VRR-MAX-NUMERATOR>` divided
+ *	by :ref:`VRR_MAX_DENOMINATOR <VRR-MAX-DENOMINATOR>` to signal a fixed
+ *	rate) should be achieved with signaling for HDMI Quick Media Switching.
+ *
+ *	Attempting to enable QMS when the attached display is not QMS capable,
+ *	or when the set fixed target rate is not possible with QMS, will result
+ *	in an error during the atomic check phase that will be passed up to
+ *	userspace to handle.
  */
 
 /**
@@ -3318,6 +3339,33 @@ int drm_connector_attach_color_format_property(struct drm_connector *connector,
 }
 EXPORT_SYMBOL(drm_connector_attach_color_format_property);
 
+/**
+ * drm_connector_attach_qms_enabled_property - attach "qms_enabled" property
+ * @connector: pointer to &struct drm_connector to attach this property on.
+ *
+ * Create and attach the "qms_enabled" property to the given @connector, which
+ * can be used by userspace to signal that it would like to achieve the CRTC's
+ * desired VRR target frame rate with HDMI "Quick Media Switching".
+ *
+ * Should only be called if the source actually supports generating the packets
+ * needed for QMS, but is present regardless of sink support for QMS.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int drm_connector_attach_qms_enabled_property(struct drm_connector *connector)
+{
+	connector->qms_enabled_property =
+		drm_property_create_bool(connector->dev, 0, "qms_enabled");
+	if (!connector->qms_enabled_property)
+		return -ENOMEM;
+
+	drm_object_attach_property(&connector->base, connector->qms_enabled_property, 0);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_qms_enabled_property);
+
 /**
  * drm_connector_atomic_hdr_metadata_equal - checks if the hdr metadata changed
  * @old_state: old connector state to compare
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 8439c82db905..a5631bebb4df 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1238,6 +1238,18 @@ 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;
+
+	/**
+	 * @qms_enabled: Whether or not userspace requests to use HDMI Quick
+	 * Media Switching to achieve the target frame rate.
+	 */
+	bool qms_enabled;
 };
 
 /**
@@ -2490,6 +2502,16 @@ struct drm_connector {
 	 */
 	struct drm_property *vrr_capable_property;
 
+	/**
+	 * @qms_enabled_property: Optional atomic boolean property through which
+	 * userspace can indicate that HDMI "Quick Media Switching" should be
+	 * used to achieve the CRTC's target frame rate.
+	 *
+	 * Only HDMI connectors of sources capable of generating the right HDMI
+	 * signalling packets should register this property.
+	 */
+	struct drm_property *qms_enabled_property;
+
 	/**
 	 * @colorspace_property: Connector property to set the suitable
 	 * colorspace supported by the sink.
@@ -3016,6 +3038,7 @@ const char *drm_get_colorspace_name(enum drm_colorspace colorspace);
 
 int drm_connector_attach_color_format_property(struct drm_connector *connector,
 					       unsigned long supported_color_formats);
+int drm_connector_attach_qms_enabled_property(struct drm_connector *connector);
 
 /**
  * drm_for_each_connector_iter - connector_list iterator macro

-- 
2.55.0


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

* [PATCH RFC 18/25] video/hdmi: Add support for QMS in VTEM EMP packing
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (16 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 17/25] drm/connector: Add "qms_enabled" drm property Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 19/25] drm/connector: hdmi: Add QMS to VTEM EMP generation Nicolas Frattaroli
                   ` (6 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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.55.0


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

* [PATCH RFC 19/25] drm/connector: hdmi: Add QMS to VTEM EMP generation
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (17 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 18/25] video/hdmi: Add support for QMS in VTEM EMP packing Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 20/25] drm/connector: hdmi: Add QMS state validation and computation Nicolas Frattaroli
                   ` (5 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

If userspace indicates that QMS is desired, 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 | 10 +++++++++-
 1 file changed, 9 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 7dbd678cf061..7237070fc9ee 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -897,7 +897,15 @@ static int hdmi_generate_emp_infoframe_vtem(const struct drm_connector *connecto
 		return 0;
 	}
 
-	vtem->game_vrr_en = true;
+	if (conn_state->hdmi.qms_enabled) {
+		vtem->game_vrr_en = false;
+		vtem->qms_en = true;
+		vtem->next_tfr = conn_state->hdmi.qms_tfr_byte;
+	} else {
+		vtem->game_vrr_en = true;
+		vtem->qms_en = false;
+		vtem->next_tfr = 0;
+	}
 
 	vtem->m_const = !vrr->dynamic;
 

-- 
2.55.0


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

* [PATCH RFC 20/25] drm/connector: hdmi: Add QMS state validation and computation
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (18 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 19/25] drm/connector: hdmi: Add QMS to VTEM EMP generation Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 21/25] drm/rockchip: dw_hdmi_qp: Add QMS support Nicolas Frattaroli
                   ` (4 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 103 +++++++++++++++++++++++-
 1 file changed, 102 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 7237070fc9ee..d392ae2908f9 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>
@@ -950,6 +951,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
@@ -988,6 +1009,69 @@ 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->hdmi.qms_enabled)
+		return 0;
+
+	if (!info->hdmi.qms_capable)
+		return -EOPNOTSUPP;
+
+	if (!drm_crtc_helper_vrr_is_fixed_rate(&crtc_state->vrr_state))
+		return -EINVAL;
+
+	if (info->hdmi.qms_tfr_min)
+		min_rate_n = 24000;
+	else
+		min_rate_n = info->monitor_range.min_vfreq * 1000;
+
+	if (info->hdmi.qms_tfr_max)
+		max_rate = info->monitor_range.max_vfreq;
+	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)
@@ -1093,6 +1177,8 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 {
 	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_state *old_crtc_state =
@@ -1113,6 +1199,7 @@ static int hdmi_validate_vrr(struct drm_connector *connector,
 
 	/* Save on the expensive vic lookup, if nothing else. */
 	if (!new_crtc_state->mode_changed &&
+	    !new_crtc_state->connectors_changed &&
 	    old_crtc_state->vrr_enabled &&
 	    old_vrr_state->vrr_min_n == vrr_state->vrr_min_n &&
 	    old_vrr_state->vrr_min_d == vrr_state->vrr_min_d &&
@@ -1143,7 +1230,9 @@ 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.cinema_vrr)
+	if (is_fixed && (info->hdmi.cinema_vrr || (info->hdmi.qms_tfr_min &&
+						   new_conn_state->hdmi.qms_enabled &&
+						   info->hdmi.qms_capable)))
 		min_vfreq_n = 24;
 
 	if (!min_vfreq_n) {
@@ -1165,6 +1254,18 @@ 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 (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.55.0


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

* [PATCH RFC 21/25] drm/rockchip: dw_hdmi_qp: Add QMS support
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (19 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 20/25] drm/connector: hdmi: Add QMS state validation and computation Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 22/25] drm/tests: hdmi: Add "Game Mode" VRR tests Nicolas Frattaroli
                   ` (3 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

Register the "qms_enabled" property on RK3588. This property can then be
used by userspace to request that a given VRR target frame rate on a
CRTC should be achieved through QMS. The common HDMI state helpers
implement the actual functionality for this.

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

diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
index 2b9432aa8c54..01ed7c18a7e0 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
@@ -521,6 +521,7 @@ struct rockchip_hdmi_qp_cfg {
 	const struct rockchip_hdmi_qp_ctrl_ops *ctrl_ops;
 	const struct dw_hdmi_qp_phy_ops *phy_ops;
 	bool supports_vrr;
+	bool supports_qms;
 };
 
 static const struct rockchip_hdmi_qp_cfg rk3576_hdmi_cfg = {
@@ -541,6 +542,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,
+	.supports_qms = true,
 };
 
 static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = {
@@ -709,6 +711,13 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
 					     "Failed to attach 'vrr_capable' property\n");
 	}
 
+	if (cfg->supports_qms) {
+		ret = drm_connector_attach_qms_enabled_property(connector);
+		if (ret)
+			return dev_err_probe(hdmi->dev, ret,
+					     "Failed to attach 'qms_enabled' property\n");
+	}
+
 	return 0;
 }
 

-- 
2.55.0


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

* [PATCH RFC 22/25] drm/tests: hdmi: Add "Game Mode" VRR tests
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (20 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 21/25] drm/rockchip: dw_hdmi_qp: Add QMS support Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 23/25] drm/tests: hdmi: Add Fixed/Constrained rate " Nicolas Frattaroli
                   ` (2 subsequent siblings)
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 264 +++++++++++++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.h             | 139 +++++++++++
 2 files changed, 403 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 b2e347863a77..1486c16c2f78 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -7,6 +7,7 @@
 #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>
@@ -3423,11 +3424,274 @@ static struct kunit_suite drm_atomic_helper_connector_hdmi_infoframes_test_suite
 	.test_cases	= drm_atomic_helper_connector_hdmi_infoframes_tests,
 };
 
+/*
+ * 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_device *drm;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * 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;
+	struct drm_device *drm;
+	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_4k_rgb_yuv420_dc_max_340mhz);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.vrr_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	KUNIT_EXPECT_EQ(test, ret, -EOPNOTSUPP);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * 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;
+	struct drm_connector *conn;
+	struct drm_device *drm;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	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_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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+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.h b/drivers/gpu/drm/tests/drm_kunit_edid.h
index 28b4df93a555..10175c9e1b2e 100644
--- a/drivers/gpu/drm/tests/drm_kunit_edid.h
+++ b/drivers/gpu/drm/tests/drm_kunit_edid.h
@@ -13,4 +13,143 @@ 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];
 
+/*
+ * 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
+ */
+static 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
+};
+
 #endif // DRM_KUNIT_EDID_H_

-- 
2.55.0


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

* [PATCH RFC 23/25] drm/tests: hdmi: Add Fixed/Constrained rate VRR tests
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (21 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 22/25] drm/tests: hdmi: Add "Game Mode" VRR tests Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 24/25] drm/tests: hdmi: Add Quick Media Switching tests Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 25/25] drm/atomic: Disable VRR in helper_set_config Nicolas Frattaroli
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 532 +++++++++++++++++++++
 1 file changed, 532 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 1486c16c2f78..e2d98280ea23 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,542 @@ static void drm_test_check_hdmi_vrr_flush_vsync(struct kunit *test)
 	drm_modeset_acquire_fini(&ctx);
 }
 
+/*
+ * 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;
+	struct drm_connector *conn;
+	struct drm_device *drm;
+	struct drm_crtc *crtc;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.cinema_vrr);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	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_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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * 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;
+	struct drm_connector *conn;
+	struct drm_device *drm;
+	struct drm_crtc *crtc;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	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_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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	/* Try a second commit to evaluate the dynamic flag */
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable_2:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable_2;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state_2:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state_2;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state_2;
+	}
+	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_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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+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" },
+};
+
+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;
+	struct drm_device *drm;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_LT(test, 24, conn->display_info.monitor_range.min_vfreq);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+	/*
+	 * Allow individual test parameters to play with the CinemaVRR flag
+	 * without needing to bring their own modified EDID.
+	 */
+	conn->display_info.hdmi.cinema_vrr = params->cinema_vrr_override;
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	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_crtc_state;
+	}
+	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;
+	struct drm_device *drm;
+	struct drm_crtc *crtc;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.cinema_vrr);
+	KUNIT_ASSERT_LT(test, 24, conn->display_info.monitor_range.min_vfreq);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	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));
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * 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;
+	struct drm_device *drm;
+	struct drm_crtc *crtc;
+	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);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	mode_refresh = drm_mode_vrefresh(preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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_crtc_state;
+	}
+	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_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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
 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.55.0


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

* [PATCH RFC 24/25] drm/tests: hdmi: Add Quick Media Switching tests
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (22 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 23/25] drm/tests: hdmi: Add Fixed/Constrained rate " Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  2026-09-21 15:51 ` [PATCH RFC 25/25] drm/atomic: Disable VRR in helper_set_config Nicolas Frattaroli
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  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 | 587 +++++++++++++++++++++
 drivers/gpu/drm/tests/drm_kunit_edid.h             | 140 +++++
 2 files changed, 727 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 e2d98280ea23..961a4b48afd8 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -4200,6 +4200,587 @@ static void drm_test_check_hdmi_vrr_constrained_rate(struct kunit *test)
 	drm_modeset_acquire_fini(&ctx);
 }
 
+/*
+ * Check that on a QMS-capable sink, an atomic commit with the qms_enabled
+ * connector property set 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;
+	struct drm_device *drm;
+	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_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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;
+
+retry_conn_state:
+	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_conn_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->hdmi.qms_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Check that on a QMS-capable sink, an atomic commit with the qms_enabled
+ * connector property set 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;
+	struct drm_device *drm;
+	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_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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;
+
+retry_conn_state:
+	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_conn_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->hdmi.qms_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Check that on a QMS-capable sink, an atomic commit with the qms_enabled
+ * connector property set 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;
+	struct drm_device *drm;
+	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_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_capable);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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;
+
+retry_conn_state:
+	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_conn_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->hdmi.qms_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * Check that on a QMS-capable sink with QMS TFR min set, an atomic commit with
+ * the qms_enabled connector property set 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;
+	struct drm_device *drm;
+	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_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_tfr_min);
+	/*
+	 * 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.cinema_vrr);
+
+	KUNIT_ASSERT_LT(test, 24, conn->display_info.monitor_range.min_vfreq);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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;
+
+retry_conn_state:
+	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_conn_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->hdmi.qms_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * 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;
+	struct drm_device *drm;
+	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_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_capable);
+	KUNIT_ASSERT_FALSE(test, conn->display_info.hdmi.qms_tfr_max);
+
+	KUNIT_ASSERT_LE(test, 100, conn->display_info.monitor_range.max_vfreq);
+
+	ret = drm_connector_attach_qms_enabled_property(conn);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, 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;
+
+retry_conn_state:
+	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_conn_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->hdmi.qms_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_EXPECT_LT(test, ret, 0);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
+/*
+ * 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;
+	struct drm_device *drm;
+	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_qms);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+
+	drm = &priv->drm;
+	crtc = priv->crtc;
+	conn = &priv->connector;
+	KUNIT_ASSERT_TRUE(test, conn->display_info.is_hdmi);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.vrr_capable);
+	KUNIT_ASSERT_TRUE(test, conn->display_info.hdmi.qms_capable);
+
+	KUNIT_ASSERT_LE(test, 100, conn->display_info.monitor_range.max_vfreq);
+
+	preferred = find_preferred_mode(conn);
+	KUNIT_ASSERT_NOT_NULL(test, preferred);
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+retry_conn_enable:
+	ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn,
+						     preferred, &ctx);
+	if (ret == -EDEADLK) {
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_conn_enable;
+	}
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+	/* Manual override to not have to define an additional EDID. */
+	conn->display_info.hdmi.qms_tfr_max = true;
+
+retry_crtc_state:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (PTR_ERR(crtc_state) == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+
+	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;
+
+retry_conn_state:
+	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_conn_state;
+	}
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+
+	conn_state->hdmi.qms_enabled = true;
+
+	ret = drm_atomic_commit(state);
+	if (ret == -EDEADLK) {
+		drm_atomic_commit_clear(state);
+		ret = drm_modeset_backoff(&ctx);
+		if (!ret)
+			goto retry_crtc_state;
+	}
+	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);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+
 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),
@@ -4210,6 +4791,12 @@ 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),
 	{ }
 };
 
diff --git a/drivers/gpu/drm/tests/drm_kunit_edid.h b/drivers/gpu/drm/tests/drm_kunit_edid.h
index 10175c9e1b2e..71029101a4ab 100644
--- a/drivers/gpu/drm/tests/drm_kunit_edid.h
+++ b/drivers/gpu/drm/tests/drm_kunit_edid.h
@@ -152,4 +152,144 @@ static const unsigned char test_edid_hdmi_vrr[] = {
 	0x00, 0x00, 0x00, 0x68
 };
 
+/*
+ * 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
+ */
+static 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
+};
+
 #endif // DRM_KUNIT_EDID_H_

-- 
2.55.0


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

* [PATCH RFC 25/25] drm/atomic: Disable VRR in helper_set_config
  2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
                   ` (23 preceding siblings ...)
  2026-09-21 15:51 ` [PATCH RFC 24/25] drm/tests: hdmi: Add Quick Media Switching tests Nicolas Frattaroli
@ 2026-09-21 15:51 ` Nicolas Frattaroli
  24 siblings, 0 replies; 29+ messages in thread
From: Nicolas Frattaroli @ 2026-09-21 15:51 UTC (permalink / raw)
  To: Borah, Chaitanya Kumar, Leo Li, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel,
	Nicolas Frattaroli

The in-kernel DRM clients do not know how to deal with VRR properties.
This means that a DRM client that does, exits, and does not clean up
after itself, will leave the KMS fbdev console non-functional.

Address this by simply setting crtc_state->vrr_enabled to false in
__drm_atomic_helper_set_config.

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

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 9bb761eda59f..1d3c35f7788e 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -2051,6 +2051,8 @@ int __drm_atomic_helper_set_config(struct drm_mode_set *set,
 
 	crtc_state->active = true;
 
+	crtc_state->vrr_enabled = false;
+
 	ret = drm_atomic_set_crtc_for_plane(primary_state, crtc);
 	if (ret != 0)
 		return ret;

-- 
2.55.0


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

* Re: [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change
  2026-09-21 15:51 ` [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change Nicolas Frattaroli
@ 2026-09-21 21:59   ` Leo Li
  2026-09-21 22:01   ` Leo Li
  1 sibling, 0 replies; 29+ messages in thread
From: Leo Li @ 2026-09-21 21:59 UTC (permalink / raw)
  To: Nicolas Frattaroli, Borah, Chaitanya Kumar, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel

Hi Nicholas,

Thanks for sending this out. I'm looking into an amdgpu implementation
and had some comments along the way. Will reply inline on respective
patches.

On 2026-09-21 11:51, Nicolas Frattaroli wrote:
> A change in whether VRR is enabled or not on a CRTC will be of interest
> to any VRR implementation, as they may need to recalculate certain
> internal state in response. So far, this was handled ad-hoc in the
> drivers that implement VRR.
> 
> Handle this in the common DRM atomic core by setting the mode_changed
> flag when this occurs. The rationale for using mode_changed is that VRR
> is deeply intertwined with the current mode with regards to its timing.
> Consequently, any component that cares about a mode change is likely
> also going to care about VRR being enabled/disabled.
> 
> Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> ---
>  drivers/gpu/drm/drm_atomic_helper.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 9d006f98413a..0664d5779282 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -694,6 +694,12 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
>  			new_crtc_state->active_changed = true;
>  		}
>  
> +		if (old_crtc_state->vrr_enabled != new_crtc_state->vrr_enabled) {
> +			drm_dbg_atomic(dev, "[CRTC:%d:%s] VRR changed\n",
> +				       crtc->base.id, crtc->name);
> +			new_crtc_state->mode_changed = true;
> +		}
> +

I don't think this is the case for all hardware, at least not for amdgpu.
The parts in HW that adjust vtotal can be programmed without requiring
bandwidth recalculations or reprogramming of pipes. 

- Leo

>  		if (new_crtc_state->enable != has_connectors) {
>  			drm_dbg_atomic(dev, "[CRTC:%d:%s] enabled/connectors mismatch (%d/%d)\n",
>  				       crtc->base.id, crtc->name,
> 


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

* Re: [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change
  2026-09-21 15:51 ` [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change Nicolas Frattaroli
  2026-09-21 21:59   ` Leo Li
@ 2026-09-21 22:01   ` Leo Li
  1 sibling, 0 replies; 29+ messages in thread
From: Leo Li @ 2026-09-21 22:01 UTC (permalink / raw)
  To: Nicolas Frattaroli, Borah, Chaitanya Kumar, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel

Hi Nicolas,

Thanks for sending this out. I'm looking into an amdgpu implementation
and had some comments along the way. Will reply inline on respective
patches.

On 2026-09-21 11:51, Nicolas Frattaroli wrote:
> A change in whether VRR is enabled or not on a CRTC will be of interest
> to any VRR implementation, as they may need to recalculate certain
> internal state in response. So far, this was handled ad-hoc in the
> drivers that implement VRR.
> 
> Handle this in the common DRM atomic core by setting the mode_changed
> flag when this occurs. The rationale for using mode_changed is that VRR
> is deeply intertwined with the current mode with regards to its timing.
> Consequently, any component that cares about a mode change is likely
> also going to care about VRR being enabled/disabled.
> 
> Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> ---
>  drivers/gpu/drm/drm_atomic_helper.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 9d006f98413a..0664d5779282 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -694,6 +694,12 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
>  			new_crtc_state->active_changed = true;
>  		}
>  
> +		if (old_crtc_state->vrr_enabled != new_crtc_state->vrr_enabled) {
> +			drm_dbg_atomic(dev, "[CRTC:%d:%s] VRR changed\n",
> +				       crtc->base.id, crtc->name);
> +			new_crtc_state->mode_changed = true;
> +		}
> +

I don't think this is the case for all vendors, at least not for amdgpu.
The parts in HW that adjust vtotal can be programmed without requiring
bandwidth recalculations or reprogramming of pipes. 

- Leo

>  		if (new_crtc_state->enable != has_connectors) {
>  			drm_dbg_atomic(dev, "[CRTC:%d:%s] enabled/connectors mismatch (%d/%d)\n",
>  				       crtc->base.id, crtc->name,
> 


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

* Re: [PATCH RFC 13/25] drm: Add VRR target frame rate properties
  2026-09-21 15:51 ` [PATCH RFC 13/25] drm: Add VRR target frame rate properties Nicolas Frattaroli
@ 2026-09-21 22:23   ` Leo Li
  0 siblings, 0 replies; 29+ messages in thread
From: Leo Li @ 2026-09-21 22:23 UTC (permalink / raw)
  To: Nicolas Frattaroli, Borah, Chaitanya Kumar, 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
  Cc: dri-devel, linux-kernel, linux-fbdev, linux-rockchip,
	linux-arm-kernel, kernel, Derek Foreman, wayland-devel



On 2026-09-21 11:51, Nicolas Frattaroli wrote:
> 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_uapi.c | 24 +++++++++++++++++++++
>  drivers/gpu/drm/drm_connector.c   | 45 +++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/drm_crtc.c        |  8 +++++++
>  drivers/gpu/drm/drm_mode_config.c | 20 +++++++++++++++++
>  include/drm/drm_crtc.h            |  4 ++++
>  include/drm/drm_mode_config.h     | 45 +++++++++++++++++++++++++++++++++++++++
>  6 files changed, 146 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index 1eebcf6f5e05..c9eb01d740b3 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 fc7d9fcf9d9f..5c07bd221977 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
> @@ -2579,6 +2579,51 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
>   *
>   *	The driver may place further restrictions within these minimum
>   *	and maximum bounds.
> + *
> + * 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.
> + *
> + * .. _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.
> + *	Otherwise, must also be 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. Otherwise, must also be zero.
>   */

If VRR_MIN_NUMERATOR == 0 && VRR_MAX_NUMERATOR > 0, do we interpret that as
vrr limiting is disabled?
>  
>  /**
> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
> index 63ead8ba6756..9244fbf97029 100644
> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -322,6 +322,14 @@ static int __drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *
>  					   config->prop_out_fence_ptr, 0);
>  		drm_object_attach_property(&crtc->base,
>  					   config->prop_vrr_enabled, 0);
> +		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);

Would it be better to let vendors attach these properties themselves? That way,
they will only exist if supported. Otherwise, I'm not sure what would be the
expected behavior if they're attached, but not supported.

- Leo
>  	}
>  
>  	return 0;
> 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 9d58158af459..df37f4ebb58e 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -67,6 +67,10 @@ struct drm_crtc_vrr_state {
>  	u8 vic;
>  	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;
>  };
> 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.
> 


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

end of thread, other threads:[~2026-09-21 22:23 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 15:51 [PATCH RFC 00/25] VRR Target Rate Limiter KMS uAPI and Implementation Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 01/25] drm/edid: Add a query for vrr range Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 02/25] drm: Add VRR state Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 03/25] drm/atomic-helper: Set mode_changed on vrr_enabled change Nicolas Frattaroli
2026-09-21 21:59   ` Leo Li
2026-09-21 22:01   ` Leo Li
2026-09-21 15:51 ` [PATCH RFC 04/25] video/hdmi: Add VTEM EMP packing Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 05/25] drm/bridge: Add VTEM EMP support Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 06/25] drm/connector: hdmi: Add VTEM EMP generation Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 07/25] drm/crtc-helper: Add VRR helper functions Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 08/25] drm/bridge: synopsys: Add VTEM EMP support Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 09/25] drm/connector: Add drm_display_info_is_vrr_capable Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 10/25] drm/rockchip: dw_hdmi_qp: Add VRR support Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 11/25] drm/rockchip: vop2: Enable VRR Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 12/25] drm/edid: Parse CinemaVRR flag from HDMI SCDS Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 13/25] drm: Add VRR target frame rate properties Nicolas Frattaroli
2026-09-21 22:23   ` Leo Li
2026-09-21 15:51 ` [PATCH RFC 14/25] drm: Implement VRR rate limiting Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 15/25] drm/edid: Parse QMS flag from HDMI SCDS Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 16/25] drm/edid: Parse QMS TFR min/max flags " Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 17/25] drm/connector: Add "qms_enabled" drm property Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 18/25] video/hdmi: Add support for QMS in VTEM EMP packing Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 19/25] drm/connector: hdmi: Add QMS to VTEM EMP generation Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 20/25] drm/connector: hdmi: Add QMS state validation and computation Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 21/25] drm/rockchip: dw_hdmi_qp: Add QMS support Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 22/25] drm/tests: hdmi: Add "Game Mode" VRR tests Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 23/25] drm/tests: hdmi: Add Fixed/Constrained rate " Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 24/25] drm/tests: hdmi: Add Quick Media Switching tests Nicolas Frattaroli
2026-09-21 15:51 ` [PATCH RFC 25/25] drm/atomic: Disable VRR in helper_set_config 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®