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: 4+ 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:32 ` Alan Stead [this message]
2026-10-30 20:32 ` [RFC v1 3/3] drm/i915/vrr: Allow VRR on HDMI 2.1 sinks Alan Stead
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.