From: Alan Stead <alan.stead@intel.com>
To: rodrigo.vivi@intel.com, jani.nikula@linux.intel.com,
intel-gfx@lists.freedesktop.org, intel-xe@lists.freedesktop.org,
vivek.kasireddy@intel.com, dongwon.kim@intel.com,
james.xiong@intel.com
Subject: [RFC v1 2/3] drm/i915/dip: Send the HDMI 2.1 VTEM when VRR is enabled
Date: Fri, 30 Oct 2026 13:32:21 -0700 [thread overview]
Message-ID: <20261030203314.1085262-3-alan.stead@intel.com> (raw)
In-Reply-To: <20261030203314.1085262-1-alan.stead@intel.com>
An HDMI 2.1 sink is told that VRR is active with a Video Timing Extended
Metadata (VTEM) packet. Without it, the sink treats every refresh rate
change as a new video timing and blanks while it resyncs.
Program a Class 0 (Gaming-VRR) VTEM through HDMI_EMP_CTL/HDMI_EMP_DATA/
HDMI_EMP_HEADER whenever VRR is enabled on an HDMI transcoder, and send a
final VTEM with VRR_EN=0 and END set when it is disabled. Both the
modeset and the fastset paths go through intel_vrr_enable() and
intel_vrr_disable().
Notes on the programming:
- HB0 in HDMI_EMP_HEADER must be set to the EMP packet type (0x7f). The
hardware doesn't fill it in, and sinks ignore the VTEM without it.
- MD2 bit 2 (RB in HDMI 2.1a) is reserved since HDMI 2.1b and sent as 0.
- Base_Vfront and Base_Refresh_Rate are only sent for timings without a
VIC, as required by the HDMI 2.1 spec.
- The 11 byte payload is zero padded to a full packet body.
HDMI VRR is not enabled by the driver yet, so this has no effect until
the following patch.
Signed-off-by: Alan Stead <alan.stead@intel.com>
Assisted-by: LLM
---
drivers/gpu/drm/i915/display/intel_dip.c | 75 +++++++++++++++++++
drivers/gpu/drm/i915/display/intel_dip.h | 2 +
drivers/gpu/drm/i915/display/intel_dip_regs.h | 17 +++++
drivers/gpu/drm/i915/display/intel_vrr.c | 4 +
4 files changed, 98 insertions(+)
diff --git a/drivers/gpu/drm/i915/display/intel_dip.c b/drivers/gpu/drm/i915/display/intel_dip.c
index 831881e6c15e..4748f6012337 100644
--- a/drivers/gpu/drm/i915/display/intel_dip.c
+++ b/drivers/gpu/drm/i915/display/intel_dip.c
@@ -4,11 +4,14 @@
*
*/
+#include <linux/minmax.h>
+
#include <drm/drm_print.h>
#include "intel_de.h"
#include "intel_dip.h"
#include "intel_dip_regs.h"
+#include "intel_display_regs.h"
#include "intel_display_types.h"
#include "intel_hdmi.h"
@@ -74,6 +77,78 @@ void intel_dip_write_emp_as_sdp_tl(const struct intel_crtc_state *crtc_state)
EMP_AS_SDP_DB_TL(crtc_state->dip.emp_as_sdp_tl));
}
+/* VTEM: 7 byte EM Data Set header (PB0-PB6) + 4 byte data set (MD0-MD3) */
+#define VTEM_DATA_SET_LENGTH 4
+#define VTEM_EMP_SIZE (7 + VTEM_DATA_SET_LENGTH)
+/* Payload bytes carried by one EMP data island packet */
+#define HDMI_EMP_PACKET_BODY 28
+
+/*
+ * Tell an HDMI 2.1 sink whether VRR is active with a Class 0 (Gaming-VRR)
+ * Video Timing Extended Metadata (VTEM) packet. It is a periodic
+ * pseudo-static EM Data Set: the hardware sends it every frame until it is
+ * reprogrammed. On disable, a last VTEM with VRR_EN=0 and END set is sent,
+ * after which the hardware stops sending it.
+ */
+void intel_dip_write_hdmi_vtem(const struct intel_crtc_state *crtc_state,
+ bool enable)
+{
+ struct intel_display *display = to_intel_display(crtc_state);
+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder;
+ const struct drm_display_mode *mode = &crtc_state->hw.adjusted_mode;
+ u8 vic = crtc_state->infoframes.avi.avi.video_code;
+ int base_rr = drm_mode_vrefresh(mode);
+ u8 pb[HDMI_EMP_PACKET_BODY] = {};
+ int i;
+
+ if (DISPLAY_VER(display) < 20 ||
+ !intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) ||
+ !crtc_state->vrr.in_range)
+ return;
+
+ /* PB0-PB6: EM Data Set header (Sync = 0, DS_Type = pseudo-static) */
+ pb[0] = BIT(7) | /* NEW */
+ BIT(2) | /* VFR */
+ (enable ? 0 : BIT(6)); /* END */
+ pb[2] = 1; /* Organization_ID: HDMI */
+ pb[4] = 1; /* Data_Set_Tag: VTEM */
+ pb[6] = VTEM_DATA_SET_LENGTH; /* Data_Set_Length */
+
+ /* PB7-PB10: MD0-MD3 (M_CONST = 0, FVA_Factor_M1 = 0, MD2 bit 2 reserved) */
+ if (enable) {
+ pb[7] = BIT(0); /* VRR_EN */
+ /* Base_Vfront and Base_Refresh_Rate are only sent without a VIC */
+ if (!vic) {
+ pb[8] = min(mode->crtc_vsync_start - mode->crtc_vdisplay, 255);
+ pb[9] = (base_rr >> 8) & 0x3;
+ pb[10] = base_rr & 0xff;
+ }
+ }
+
+ drm_dbg_kms(display->drm,
+ "[TRANSCODER %s] VTEM: VRR_EN=%d VIC=%u Base_Vfront=%u Base_RR=%u\n",
+ transcoder_name(cpu_transcoder), enable, vic, pb[8],
+ pb[9] << 8 | pb[10]);
+
+ /*
+ * HDMI_EMP_CTL first, then the payload dwords (PB0 in bits 7:0), then
+ * the header, whose write clock crosses the whole packet. HB0 must be
+ * set to the EMP packet type, the hardware doesn't fill it in.
+ */
+ intel_de_write(display, HDMI_EMP_CTL(display, cpu_transcoder),
+ HDMI_EMP_CTL_TYPE_VTEM);
+ for (i = 0; i < ARRAY_SIZE(pb); i += 4)
+ intel_de_write(display, HDMI_EMP_DATA(display, cpu_transcoder),
+ pb[i] | pb[i + 1] << 8 | pb[i + 2] << 16 |
+ pb[i + 3] << 24);
+ intel_de_write(display, HDMI_EMP_HEADER(display, cpu_transcoder),
+ HDMI_EMP_HEADER_HB0_EMP |
+ HDMI_EMP_HEADER_DS_TYPE_PSEUDO_STATIC |
+ (enable ? 0 : HDMI_EMP_HEADER_END) |
+ HDMI_EMP_HEADER_NUM_PACKETS(DIV_ROUND_UP(VTEM_EMP_SIZE,
+ HDMI_EMP_PACKET_BODY)));
+}
+
static int intel_dip_sdp_stagger_to_tl(struct intel_crtc_state *crtc_state,
int stagger)
{
diff --git a/drivers/gpu/drm/i915/display/intel_dip.h b/drivers/gpu/drm/i915/display/intel_dip.h
index 7c64c84cb1be..243bca435c1d 100644
--- a/drivers/gpu/drm/i915/display/intel_dip.h
+++ b/drivers/gpu/drm/i915/display/intel_dip.h
@@ -45,6 +45,8 @@ struct intel_crtc_state;
u16 intel_dip_read_emp_as_sdp_tl(const struct intel_crtc_state *crtc_state);
void intel_dip_write_emp_as_sdp_tl(const struct intel_crtc_state *crtc_state);
+void intel_dip_write_hdmi_vtem(const struct intel_crtc_state *crtc_state, bool enable);
+
void intel_dip_sdp_tl_compute_config_late(struct intel_crtc_state *crtc_state);
void intel_dip_sdp_transmission_line_get_config(struct intel_crtc_state *crtc_state);
void intel_dip_cmn_sdp_transmission_line_enable(const struct intel_crtc_state *crtc_state);
diff --git a/drivers/gpu/drm/i915/display/intel_dip_regs.h b/drivers/gpu/drm/i915/display/intel_dip_regs.h
index d7e40a613913..273daaab8234 100644
--- a/drivers/gpu/drm/i915/display/intel_dip_regs.h
+++ b/drivers/gpu/drm/i915/display/intel_dip_regs.h
@@ -15,6 +15,23 @@
#define EMP_AS_SDP_DB_TL(db_transmit_line) REG_FIELD_PREP(EMP_AS_SDP_DB_TL_MASK, \
(db_transmit_line))
+/* HDMI 2.1 Extended Metadata Packet (EMP) control/header; payload is HDMI_EMP_DATA */
+#define _HDMI_EMP_CTL_A 0x600d0
+#define HDMI_EMP_CTL(display, trans) _MMIO_TRANS2((display), (trans), _HDMI_EMP_CTL_A)
+#define HDMI_EMP_CTL_TYPE_MASK REG_GENMASK(3, 0)
+#define HDMI_EMP_CTL_TYPE_VTEM REG_FIELD_PREP(HDMI_EMP_CTL_TYPE_MASK, 1)
+
+#define _HDMI_EMP_HEADER_A 0x600d4
+#define HDMI_EMP_HEADER(display, trans) _MMIO_TRANS2((display), (trans), _HDMI_EMP_HEADER_A)
+#define HDMI_EMP_HEADER_END REG_BIT(26)
+#define HDMI_EMP_HEADER_DS_TYPE_MASK REG_GENMASK(25, 24)
+#define HDMI_EMP_HEADER_DS_TYPE_PSEUDO_STATIC REG_FIELD_PREP(HDMI_EMP_HEADER_DS_TYPE_MASK, 0)
+#define HDMI_EMP_HEADER_NUM_PACKETS_MASK REG_GENMASK(23, 16)
+#define HDMI_EMP_HEADER_NUM_PACKETS(n) REG_FIELD_PREP(HDMI_EMP_HEADER_NUM_PACKETS_MASK, \
+ (n))
+#define HDMI_EMP_HEADER_HB0_MASK REG_GENMASK(7, 0)
+#define HDMI_EMP_HEADER_HB0_EMP REG_FIELD_PREP(HDMI_EMP_HEADER_HB0_MASK, 0x7f)
+
/* COMMON SDP TRANSMISSION LINE */
#define _CMN_SDP_TL_A 0x6020c
#define CMN_SDP_TL(display, trans) _MMIO_TRANS2(display, (trans), _CMN_SDP_TL_A)
diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c
index b14750692ae0..f4cc4148d3a1 100644
--- a/drivers/gpu/drm/i915/display/intel_vrr.c
+++ b/drivers/gpu/drm/i915/display/intel_vrr.c
@@ -1010,6 +1010,8 @@ void intel_vrr_enable(const struct intel_crtc_state *crtc_state)
if (!intel_vrr_always_use_vrr_tg(display))
intel_vrr_tg_enable(crtc_state);
+
+ intel_dip_write_hdmi_vtem(crtc_state, true);
}
void intel_vrr_disable(const struct intel_crtc_state *old_crtc_state)
@@ -1024,6 +1026,8 @@ void intel_vrr_disable(const struct intel_crtc_state *old_crtc_state)
intel_vrr_disable_dc_balancing(old_crtc_state);
intel_vrr_set_fixed_rr_timings(old_crtc_state, old_crtc_state->cpu_transcoder);
+
+ intel_dip_write_hdmi_vtem(old_crtc_state, false);
}
void intel_vrr_transcoder_enable(const struct intel_crtc_state *crtc_state)
--
2.53.0
next prev parent reply other threads:[~2026-10-30 20:34 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 20:32 [RFC v1 0/3] drm/i915/vrr: HDMI 2.1 VRR support Alan Stead
2026-10-30 20:32 ` [RFC v1 1/3] drm/i915/dip: Send HDMI EMPs early in V. Blank when VRR is in range Alan Stead
2026-10-30 20:46 ` sashiko-bot
2026-10-30 20:32 ` Alan Stead [this message]
2026-10-30 20:59 ` [RFC v1 2/3] drm/i915/dip: Send the HDMI 2.1 VTEM when VRR is enabled sashiko-bot
2026-10-30 20:32 ` [RFC v1 3/3] drm/i915/vrr: Allow VRR on HDMI 2.1 sinks Alan Stead
2026-10-30 20:58 ` sashiko-bot
2026-10-30 21:09 ` ✓ CI.KUnit: success for drm/i915/vrr: HDMI 2.1 VRR support Patchwork
2026-10-30 21:45 ` ✓ Xe.CI.BAT: " Patchwork
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=20261030203314.1085262-3-alan.stead@intel.com \
--to=alan.stead@intel.com \
--cc=dongwon.kim@intel.com \
--cc=intel-gfx@lists.freedesktop.org \
--cc=intel-xe@lists.freedesktop.org \
--cc=james.xiong@intel.com \
--cc=jani.nikula@linux.intel.com \
--cc=rodrigo.vivi@intel.com \
--cc=vivek.kasireddy@intel.com \
/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