[PATCH 1/3] drm/amd/display: support HDMI 1.4 3D modes on HDMI connectors
From: Adrian Betschart
Date: Mon Sep 07 2026 - 07:19:45 EST
Allow the stereo modes drm_edid derives from a sink's HDMI VSDB on
HDMI connectors, and drive them the way a 2D stream is driven: the
source (compositor or media player) lays both views out in the frame
itself - side by side, top and bottom, or the doubled frame-packing
timing with the 45-line active space between the eyes - and the only
3D-specific output is the HDMI vendor infoframe that tells the sink
how the frame is laid out.
Keep the DC timing at TIMING_3D_FORMAT_NONE for all of them. Any DC
stereo timing format, including the SW_PACKED variants, makes the
hardware treat the surface as two views: the pipe is split and both
view addresses point at the same surface, so the whole frame ends up
in each half of the output. The new vsif_3d_format stream field
carries the layout to mod_build_hf_vsif_infopacket() instead, so the
VSIF is right and stays right when the freesync code rebuilds it for
ALLM.
Frame packing needs the doubled CRTC timing; pass CRTC_STEREO_DOUBLE
wherever amdgpu_dm recomputes the CRTC fields itself.
Limit stereo_allowed to native HDMI connectors: on the DP-to-HDMI
converter paths link validation rejects the 3D timings.
Tested on a Radeon RX 7600 (DCN 3.2.1) driving a JVC DLA-RS4100
projector through an HDFury VRROOM, with Kodi rendering the packed
frames: frame packing, top-and-bottom and side-by-side at
1920x1080p24, RGB 12 bpc, all engage the projector's 3D mode with
correct per-eye geometry and eye assignment (checked with per-eye
test patterns through shutter glasses).
Signed-off-by: Adrian Betschart <adrian.betschart@xxxxxxxxxxxx>
---
.../display/amdgpu_dm/amdgpu_dm_connector.c | 41 +++++++++++++++++--
drivers/gpu/drm/amd/display/dc/dc_stream.h | 7 ++++
.../display/modules/info_packet/info_packet.c | 3 ++
3 files changed, 48 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 0e71ba498..7a2beb175 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -851,6 +851,25 @@ STATIC_IFN_KUNIT bool adjust_colour_depth_from_display_info(
}
EXPORT_IF_KUNIT(adjust_colour_depth_from_display_info);
+/*
+ * 3D layout to announce in the HDMI vendor infoframe for a DRM 3D mode. The
+ * stream timing itself stays 2D (see fill_stream_properties_from_drm_display_mode).
+ */
+static enum dc_timing_3d_format amdgpu_dm_vsif_3d_format(unsigned int mode_flags)
+{
+ switch (mode_flags & DRM_MODE_FLAG_3D_MASK) {
+ case DRM_MODE_FLAG_3D_FRAME_PACKING:
+ return TIMING_3D_FORMAT_SW_FRAME_PACKING;
+ case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
+ return TIMING_3D_FORMAT_TB_SW_PACKED;
+ case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+ case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL:
+ return TIMING_3D_FORMAT_SBS_SW_PACKED;
+ default:
+ return TIMING_3D_FORMAT_NONE;
+ }
+}
+
STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *stream,
const struct drm_display_mode *mode_in,
@@ -887,7 +906,15 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode(
*/
timing_out->pixel_encoding = requested_encoding;
+ /*
+ * The source packs both views into the frame itself (side-by-side,
+ * top-and-bottom, or the doubled frame-packing timing), so the display
+ * core scans it out as a plain 2D stream and only the HDMI vendor
+ * infoframe tells the sink how the frame is laid out. Any DC stereo
+ * timing format would make the hardware treat the surface as two views.
+ */
timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE;
+ stream->vsif_3d_format = amdgpu_dm_vsif_3d_format(mode_in->flags);
timing_out->display_color_depth = amdgpu_dm_convert_color_depth_from_display_info(
connector,
(timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420),
@@ -1451,6 +1478,9 @@ create_stream_for_sink(struct drm_connector *connector,
struct dc_sink *sink = NULL;
drm_mode_init(&mode, drm_mode);
+ /* frame packing scans out both views plus the active space in one frame */
+ if (mode.flags & DRM_MODE_FLAG_3D_FRAME_PACKING)
+ drm_mode_set_crtcinfo(&mode, CRTC_STEREO_DOUBLE);
memset(&saved_mode, 0, sizeof(saved_mode));
if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) {
@@ -1530,7 +1560,9 @@ create_stream_for_sink(struct drm_connector *connector,
}
if (recalculate_timing)
- drm_mode_set_crtcinfo(&saved_mode, 0);
+ drm_mode_set_crtcinfo(&saved_mode,
+ (saved_mode.flags & DRM_MODE_FLAG_3D_FRAME_PACKING) ?
+ CRTC_STEREO_DOUBLE : 0);
/*
* If scaling is enabled and refresh rate didn't change
@@ -2449,7 +2481,9 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
if (!test_mode)
goto fail;
- drm_mode_set_crtcinfo(test_mode, 0);
+ drm_mode_set_crtcinfo(test_mode,
+ (test_mode->flags & DRM_MODE_FLAG_3D_FRAME_PACKING) ?
+ CRTC_STEREO_DOUBLE : 0);
stream = amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
to_dm_connector_state(connector->state),
@@ -3143,7 +3177,8 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
aconnector->dc_link = link;
aconnector->base.interlace_allowed = false;
aconnector->base.doublescan_allowed = false;
- aconnector->base.stereo_allowed = false;
+ /* HDMI 1.4 3D only; DP-to-HDMI converters reject the timings in link validation */
+ aconnector->base.stereo_allowed = connector_type == DRM_MODE_CONNECTOR_HDMIA;
aconnector->base.dpms = DRM_MODE_DPMS_OFF;
aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */
aconnector->audio_inst = -1;
diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h
index 934ae381e..e6fd75b87 100644
--- a/drivers/gpu/drm/amd/display/dc/dc_stream.h
+++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h
@@ -235,6 +235,13 @@ struct dc_stream_state {
enum view_3d_format view_format;
+ /**
+ * @vsif_3d_format: 3D layout announced in the HDMI vendor infoframe when
+ * the source packs both views into the frame itself and the timing is
+ * therefore left at TIMING_3D_FORMAT_NONE.
+ */
+ enum dc_timing_3d_format vsif_3d_format;
+
bool use_vsc_sdp_for_colorimetry;
bool ignore_msa_timing_param;
diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c
index 32b697f46..13478b8bd 100644
--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c
+++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c
@@ -536,6 +536,9 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream,
format = stream->timing.timing_3d_format;
if (stream->view_format == VIEW_3D_FORMAT_NONE)
format = TIMING_3D_FORMAT_NONE;
+ /* a 2D scanout of a frame the source packed itself */
+ if (format == TIMING_3D_FORMAT_NONE)
+ format = stream->vsif_3d_format;
if (stream->timing.hdmi_vic != 0
&& stream->timing.h_total >= 3840
--
2.43.0