From: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
To: "Borah, Chaitanya Kumar" <chaitanya.kumar.borah@intel.com>,
"Leo Li" <sunpeng.li@amd.com>,
"Daniel Stone" <daniels@collabora.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>, "Helge Deller" <deller@gmx.de>,
"Andrzej Hajda" <andrzej.hajda@intel.com>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Robert Foss" <rfoss@kernel.org>,
"Laurent Pinchart" <Laurent.pinchart@ideasonboard.com>,
"Jonas Karlman" <jonas@kwiboo.se>,
"Jernej Skrabec" <jernej.skrabec@gmail.com>,
"Luca Ceresoli" <luca.ceresoli@bootlin.com>,
"Sandy Huang" <hjc@rock-chips.com>,
"Heiko Stübner" <heiko@sntech.de>,
"Andy Yan" <andy.yan@rock-chips.com>
Cc: dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
linux-fbdev@vger.kernel.org, linux-rockchip@lists.infradead.org,
linux-arm-kernel@lists.infradead.org, kernel@collabora.com,
Derek Foreman <derek.foreman@collabora.com>,
wayland-devel@lists.freedesktop.org,
Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Subject: [PATCH RFC 14/25] drm: Implement VRR rate limiting
Date: Mon, 21 Sep 2026 17:51:40 +0200 [thread overview]
Message-ID: <20260921-vrr-limiter-uapi-v1-14-2fcd7d011646@collabora.com> (raw)
In-Reply-To: <20260921-vrr-limiter-uapi-v1-0-2fcd7d011646@collabora.com>
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
next prev parent reply other threads:[~2026-09-21 15:54 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
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-22 12:53 ` Nicolas Frattaroli
2026-09-22 13:22 ` Maxime Ripard
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-22 15:26 ` Nicolas Frattaroli
2026-09-21 15:51 ` Nicolas Frattaroli [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260921-vrr-limiter-uapi-v1-14-2fcd7d011646@collabora.com \
--to=nicolas.frattaroli@collabora.com \
--cc=Laurent.pinchart@ideasonboard.com \
--cc=airlied@gmail.com \
--cc=andrzej.hajda@intel.com \
--cc=andy.yan@rock-chips.com \
--cc=chaitanya.kumar.borah@intel.com \
--cc=daniels@collabora.com \
--cc=deller@gmx.de \
--cc=derek.foreman@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=heiko@sntech.de \
--cc=hjc@rock-chips.com \
--cc=jernej.skrabec@gmail.com \
--cc=jonas@kwiboo.se \
--cc=kernel@collabora.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-fbdev@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rockchip@lists.infradead.org \
--cc=luca.ceresoli@bootlin.com \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=rfoss@kernel.org \
--cc=simona@ffwll.ch \
--cc=sunpeng.li@amd.com \
--cc=tzimmermann@suse.de \
--cc=wayland-devel@lists.freedesktop.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®