Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
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


  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