Hi,
This series introduces stereo 3D modes support and is split in 3 chunks:
1. 3 kernel patches to parse the 3D_present flag of the HDMI CEA vendor block, to expose 3D formats flags in modes and to add a new property on connectors supporting stereo 3D,
2. Sync the new mode flags in libdrm,
3. Use testdisplay from intel-gpu-tools to test the Top Bottom and Frame Packing modes,
A few notes:
- An (edited) extract of HDMI 1.4a for 3D formats is available publicly: http://www.hdmi.org/manufacturer/specification.aspx
- I tried to come up with a way to not disrupt current user space with extra 3D modes. The 3D formats (the different ways of laying out buffers with left and right frames) that a mode supports are indicated with flags in mode.flags. This also means that no extra mode is added to the mode list, the format flags are added on already exposed 2D modes.
- For now, only the 3D_present flags of the CEA HDMI block is being parsed, only exposing the then "mandatory" 3D formats (see HDMI 1.4a 3D spec)
- When scanning out a 3D framebuffer, the "select 3D mode" property attached to a connector can be used to tell the driver to send the HDMI vendor infoframes with the 3D format details.
- 3D test images are available at: http://www.quantumdata.com/apps/3D/sample_BMP.asp
- These patches have taken some inspiration from previous work of Armin Reese armin.c.reese@intel.com and Sateesh Kavuri.
Comments, suggestions and reviews are greatly appreciated!
-- Damien
From: Damien Lespiau damien.lespiau@intel.com
For now, let's just look at the 3D_present flag of the CEA HDMI vendor block to detect if the sink supports a small list of then mandatory 3D formats.
See the HDMI 1.4a 3D extraction for detail: http://www.hdmi.org/manufacturer/specification.aspx
Signed-off-by: Damien Lespiau damien.lespiau@intel.com --- drivers/gpu/drm/drm_edid.c | 84 ++++++++++++++++++++++++++++++++++++++++++++-- include/drm/drm_mode.h | 35 +++++++++++-------- 2 files changed, 103 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index b7ee230..9ffd5c8 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1522,21 +1522,101 @@ do_cea_modes (struct drm_connector *connector, u8 *db, u8 len) return modes; }
+static bool cea_hdmi_3d_present(u8 *hdmi) +{ + u8 len, skip = 0; + + len = hdmi[0] & 0x1f; + + if (len < 8) + return false; + + /* no HDMI_Video_present */ + if (!(hdmi[8] & (1<<5))) + return false; + + /* Latency_fields_present */ + if (hdmi[8] & (1 << 7)) + skip += 2; + + /* I_Latency_fields_present */ + if (hdmi[8] & (1 << 6)) + skip += 2; + + /* the declared length is not long enough */ + if (len < (9 + skip)) + return false; + + return (hdmi[9 + skip] & (1 << 7)) != 0; +} + +static const struct { + int width, height, freq; + unsigned int select, value; + unsigned int formats; +} s3d_mandatory_modes[] = { + { 1920, 1080, 24, DRM_MODE_FLAG_INTERLACE, 0, + DRM_MODE_FLAG_3D_TOP_BOTTOM | DRM_MODE_FLAG_3D_FRAME_PACKING }, + { 1920, 1080, 50, DRM_MODE_FLAG_INTERLACE, DRM_MODE_FLAG_INTERLACE, + DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF }, + { 1920, 1080, 60, DRM_MODE_FLAG_INTERLACE, DRM_MODE_FLAG_INTERLACE, + DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF }, + { 1280, 720, 50, DRM_MODE_FLAG_INTERLACE, 0, + DRM_MODE_FLAG_3D_TOP_BOTTOM | DRM_MODE_FLAG_3D_FRAME_PACKING }, + { 1280, 720, 60, DRM_MODE_FLAG_INTERLACE, 0, + DRM_MODE_FLAG_3D_TOP_BOTTOM | DRM_MODE_FLAG_3D_FRAME_PACKING } +}; + +static void cea_hdmi_patch_mandatory_3d_mode(struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s3d_mandatory_modes); i++) { + if (mode->hdisplay == s3d_mandatory_modes[i].width && + mode->vdisplay == s3d_mandatory_modes[i].height && + (mode->flags & s3d_mandatory_modes[i].select) == + s3d_mandatory_modes[i].value && + drm_mode_vrefresh(mode) == s3d_mandatory_modes[i].freq) { + mode->flags |= s3d_mandatory_modes[i].formats; + } + } +} + +static void cea_hdmi_patch_mandatory_3d_modes(struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + list_for_each_entry(mode, &connector->probed_modes, head) + cea_hdmi_patch_mandatory_3d_mode(mode); +} + static int add_cea_modes(struct drm_connector *connector, struct edid *edid) { u8 * cea = drm_find_cea_extension(edid); - u8 * db, dbl; + u8 * db, *hdmi = NULL, dbl; int modes = 0;
+ /* let's find the cea modes before looking at the hdmi vendor block + * as the 3d_present flag needs to know about the supported modes + * to infer the 3D modes */ if (cea && cea[1] >= 3) { for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) { dbl = db[0] & 0x1f; - if (((db[0] & 0xe0) >> 5) == VIDEO_BLOCK) + switch ((db[0] & 0xe0) >> 5) { + case VIDEO_BLOCK: modes += do_cea_modes (connector, db+1, dbl); + break; + case VENDOR_BLOCK: + hdmi = db; + break; + } } }
+ if (hdmi && cea_hdmi_3d_present(hdmi)) + cea_hdmi_patch_mandatory_3d_modes(connector); + return modes; }
diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index 3d6301b..04b4996 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -44,20 +44,27 @@
/* Video mode flags */ /* bit compatible with the xorg definitions. */ -#define DRM_MODE_FLAG_PHSYNC (1<<0) -#define DRM_MODE_FLAG_NHSYNC (1<<1) -#define DRM_MODE_FLAG_PVSYNC (1<<2) -#define DRM_MODE_FLAG_NVSYNC (1<<3) -#define DRM_MODE_FLAG_INTERLACE (1<<4) -#define DRM_MODE_FLAG_DBLSCAN (1<<5) -#define DRM_MODE_FLAG_CSYNC (1<<6) -#define DRM_MODE_FLAG_PCSYNC (1<<7) -#define DRM_MODE_FLAG_NCSYNC (1<<8) -#define DRM_MODE_FLAG_HSKEW (1<<9) /* hskew provided */ -#define DRM_MODE_FLAG_BCAST (1<<10) -#define DRM_MODE_FLAG_PIXMUX (1<<11) -#define DRM_MODE_FLAG_DBLCLK (1<<12) -#define DRM_MODE_FLAG_CLKDIV2 (1<<13) +#define DRM_MODE_FLAG_PHSYNC (1<<0) +#define DRM_MODE_FLAG_NHSYNC (1<<1) +#define DRM_MODE_FLAG_PVSYNC (1<<2) +#define DRM_MODE_FLAG_NVSYNC (1<<3) +#define DRM_MODE_FLAG_INTERLACE (1<<4) +#define DRM_MODE_FLAG_DBLSCAN (1<<5) +#define DRM_MODE_FLAG_CSYNC (1<<6) +#define DRM_MODE_FLAG_PCSYNC (1<<7) +#define DRM_MODE_FLAG_NCSYNC (1<<8) +#define DRM_MODE_FLAG_HSKEW (1<<9) /* hskew provided */ +#define DRM_MODE_FLAG_BCAST (1<<10) +#define DRM_MODE_FLAG_PIXMUX (1<<11) +#define DRM_MODE_FLAG_DBLCLK (1<<12) +#define DRM_MODE_FLAG_CLKDIV2 (1<<13) +#define DRM_MODE_FLAG_3D_TOP_BOTTOM (1<<14) +#define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (1<<15) +#define DRM_MODE_FLAG_3D_FRAME_PACKING (1<<16) + +#define DRM_MODE_FLAG_3D_MASK (DRM_MODE_FLAG_3D_TOP_BOTTOM | \ + DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF | \ + DRM_MODE_FLAG_3D_FRAME_PACKING)
/* DPMS flags */ /* bit compatible with the xorg definitions. */
From: Damien Lespiau damien.lespiau@intel.com
The "select 3D mode" property can be connected to connectors to signal user space that 3D framebuffers can be scanned out to the said connector.
Signed-off-by: Damien Lespiau damien.lespiau@intel.com --- drivers/gpu/drm/drm_crtc.c | 32 ++++++++++++++++++++++++++++++++ include/drm/drm_crtc.h | 4 ++++ 2 files changed, 36 insertions(+)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 6fbfc24..dcd6d81 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -841,6 +841,38 @@ int drm_mode_create_tv_properties(struct drm_device *dev, int num_modes, } EXPORT_SYMBOL(drm_mode_create_tv_properties);
+static const struct drm_prop_enum_list s3d_modes_list[] = +{ + { 0, "None" }, + { DRM_MODE_FLAG_3D_TOP_BOTTOM, "Top bottom" }, + { DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF, "Side by side (half)" }, + { DRM_MODE_FLAG_3D_FRAME_PACKING, "Frame packing" } +}; + +/** + * drm_mode_create_3d_property - create stereo 3D properties + * @dev: DRM device + * + * Called by a driver the first time it's needed, must be attached to modes + * that supports stereo 3D formats. + */ +int drm_mode_create_3d_property(struct drm_device *dev) +{ + struct drm_property *s3d_selector; + + if (dev->mode_config.s3d_select_mode_property) + return 0; + + s3d_selector = drm_property_create_enum(dev, 0, + "select 3D mode", + s3d_modes_list, + ARRAY_SIZE(s3d_modes_list)); + dev->mode_config.s3d_select_mode_property = s3d_selector; + + return 0; +} +EXPORT_SYMBOL(drm_mode_create_3d_property); + /** * drm_mode_create_scaling_mode_property - create scaling mode property * @dev: DRM device diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index bfacf0d..5a6e024 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -802,6 +802,9 @@ struct drm_mode_config { struct drm_property *tv_saturation_property; struct drm_property *tv_hue_property;
+ /* Stereo 3D properties */ + struct drm_property *s3d_select_mode_property; + /* Optional properties */ struct drm_property *scaling_mode_property; struct drm_property *dithering_mode_property; @@ -950,6 +953,7 @@ extern int drm_property_add_enum(struct drm_property *property, int index, extern int drm_mode_create_dvi_i_properties(struct drm_device *dev); extern int drm_mode_create_tv_properties(struct drm_device *dev, int num_formats, char *formats[]); +extern int drm_mode_create_3d_property(struct drm_device *dev); extern int drm_mode_create_scaling_mode_property(struct drm_device *dev); extern int drm_mode_create_dithering_property(struct drm_device *dev); extern int drm_mode_create_dirty_info_property(struct drm_device *dev);
From: Damien Lespiau damien.lespiau@intel.com
When scanning out a 3D framebuffer, send the corresponding infoframe to the HDMI sink.
See http://www.hdmi.org/manufacturer/specification.aspx for details.
Signed-off-by: Damien Lespiau damien.lespiau@intel.com --- drivers/gpu/drm/i915/intel_drv.h | 14 ++++++++ drivers/gpu/drm/i915/intel_hdmi.c | 71 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index cd54cf8..76d488e 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -263,6 +263,13 @@ struct cxsr_latency { #define DIP_SPD_BD 0xa #define DIP_SPD_SCD 0xb
+#define DIP_TYPE_VENDOR 0x81 +#define DIP_VERSION_VENDOR 0x1 +#define DIP_HDMI_3D_PRESENT (0x2<<4) +#define DIP_HDMI_3D_STRUCT_FP (0x0<<4) +#define DIP_HDMI_3D_STRUCT_TB (0x6<<4) +#define DIP_HDMI_3D_STRUCT_SBSH (0x8<<4) + struct dip_infoframe { uint8_t type; /* HB0 */ uint8_t ver; /* HB1 */ @@ -292,6 +299,12 @@ struct dip_infoframe { uint8_t pd[16]; uint8_t sdi; } __attribute__ ((packed)) spd; + struct { + uint8_t vendor_id[3]; + uint8_t video_format; + uint8_t s3d_struct; + uint8_t s3d_ext_data; + } __attribute__ ((packed)) hdmi; uint8_t payload[27]; } __attribute__ ((packed)) body; } __attribute__((packed)); @@ -305,6 +318,7 @@ struct intel_hdmi { bool has_hdmi_sink; bool has_audio; enum hdmi_force_audio force_audio; + unsigned int s3d_mode; void (*write_infoframe)(struct drm_encoder *encoder, struct dip_infoframe *frame); void (*set_infoframes)(struct drm_encoder *encoder, diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 98f6024..ab0553d 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -83,6 +83,8 @@ static u32 g4x_infoframe_index(struct dip_infoframe *frame) return VIDEO_DIP_SELECT_AVI; case DIP_TYPE_SPD: return VIDEO_DIP_SELECT_SPD; + case DIP_TYPE_VENDOR: + return VIDEO_DIP_SELECT_VENDOR; default: DRM_DEBUG_DRIVER("unknown info frame type %d\n", frame->type); return 0; @@ -96,6 +98,8 @@ static u32 g4x_infoframe_enable(struct dip_infoframe *frame) return VIDEO_DIP_ENABLE_AVI; case DIP_TYPE_SPD: return VIDEO_DIP_ENABLE_SPD; + case DIP_TYPE_VENDOR: + return VIDEO_DIP_ENABLE_VENDOR; default: DRM_DEBUG_DRIVER("unknown info frame type %d\n", frame->type); return 0; @@ -338,6 +342,51 @@ static void intel_hdmi_set_spd_infoframe(struct drm_encoder *encoder) intel_set_infoframe(encoder, &spd_if); }
+static void intel_hdmi_set_hdmi_infoframe(struct drm_encoder *encoder) +{ + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + struct dip_infoframe hdmi_if; + + /* We really only need to send a HDMI vendor info frame when having + * a 3D format to describe */ + if (!intel_hdmi->s3d_mode) + return; + + memset(&hdmi_if, 0, sizeof(hdmi_if)); + hdmi_if.type = DIP_TYPE_VENDOR; + hdmi_if.ver = DIP_VERSION_VENDOR; + /* HDMI IEEE registration id, least significant bit first */ + hdmi_if.body.hdmi.vendor_id[0] = 0x03; + hdmi_if.body.hdmi.vendor_id[1] = 0xc0; + hdmi_if.body.hdmi.vendor_id[2] = 0x00; + hdmi_if.body.hdmi.video_format = DIP_HDMI_3D_PRESENT; + if (intel_hdmi->s3d_mode & DRM_MODE_FLAG_3D_FRAME_PACKING) + hdmi_if.body.hdmi.s3d_struct = DIP_HDMI_3D_STRUCT_FP; + else if (intel_hdmi->s3d_mode & DRM_MODE_FLAG_3D_TOP_BOTTOM) + hdmi_if.body.hdmi.s3d_struct = DIP_HDMI_3D_STRUCT_TB; + else if (intel_hdmi->s3d_mode & DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF) + hdmi_if.body.hdmi.s3d_struct = DIP_HDMI_3D_STRUCT_SBSH; + /* len is the payload len, not including checksum. Side by side (half) + * has an extra byte for 3D_Ext_Data */ + if (intel_hdmi->s3d_mode & DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF) { + hdmi_if.len = 6; + /* SBSH is subsampled by a factor of 2 */ + hdmi_if.body.hdmi.s3d_ext_data = 2 << 4; + } else + hdmi_if.len = 5; + + DRM_DEBUG_DRIVER("3D payload (len %d) %02x %02x %02x %02x %02x %02x\n", + hdmi_if.len, + hdmi_if.body.payload[0], + hdmi_if.body.payload[1], + hdmi_if.body.payload[2], + hdmi_if.body.payload[3], + hdmi_if.body.payload[4], + hdmi_if.body.payload[5]); + + intel_set_infoframe(encoder, &hdmi_if); +} + static void g4x_set_infoframes(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { @@ -398,6 +447,7 @@ static void g4x_set_infoframes(struct drm_encoder *encoder,
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode); intel_hdmi_set_spd_infoframe(encoder); + intel_hdmi_set_hdmi_infoframe(encoder); }
static void ibx_set_infoframes(struct drm_encoder *encoder, @@ -457,6 +507,7 @@ static void ibx_set_infoframes(struct drm_encoder *encoder,
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode); intel_hdmi_set_spd_infoframe(encoder); + intel_hdmi_set_hdmi_infoframe(encoder); }
static void cpt_set_infoframes(struct drm_encoder *encoder, @@ -492,6 +543,7 @@ static void cpt_set_infoframes(struct drm_encoder *encoder,
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode); intel_hdmi_set_spd_infoframe(encoder); + intel_hdmi_set_hdmi_infoframe(encoder); }
static void vlv_set_infoframes(struct drm_encoder *encoder, @@ -526,6 +578,7 @@ static void vlv_set_infoframes(struct drm_encoder *encoder,
intel_hdmi_set_avi_infoframe(encoder, adjusted_mode); intel_hdmi_set_spd_infoframe(encoder); + intel_hdmi_set_hdmi_infoframe(encoder); }
static void hsw_set_infoframes(struct drm_encoder *encoder, @@ -792,7 +845,8 @@ intel_hdmi_set_property(struct drm_connector *connector, uint64_t val) { struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector); - struct drm_i915_private *dev_priv = connector->dev->dev_private; + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = dev->dev_private; int ret;
ret = drm_connector_property_set_value(connector, property, val); @@ -828,6 +882,15 @@ intel_hdmi_set_property(struct drm_connector *connector, goto done; }
+ + if (property == dev->mode_config.s3d_select_mode_property) { + if (val == intel_hdmi->s3d_mode) + return 0; + + intel_hdmi->s3d_mode = val; + goto done; + } + return -EINVAL;
done: @@ -885,8 +948,14 @@ static const struct drm_encoder_funcs intel_hdmi_enc_funcs = { static void intel_hdmi_add_properties(struct intel_hdmi *intel_hdmi, struct drm_connector *connector) { + struct drm_device *dev = connector->dev; + intel_attach_force_audio_property(connector); intel_attach_broadcast_rgb_property(connector); + drm_mode_create_3d_property(dev); + drm_object_attach_property(&connector->base, + dev->mode_config.s3d_select_mode_property, + 0); }
void intel_hdmi_init(struct drm_device *dev, int sdvox_reg)
On Wed, 12 Sep 2012 18:47:12 +0100, Damien Lespiau damien.lespiau@gmail.com wrote:
Hi,
This series introduces stereo 3D modes support and is split in 3 chunks:
- 3 kernel patches to parse the 3D_present flag of the HDMI CEA vendor block, to expose 3D formats flags in modes and to add a new property on connectors supporting stereo 3D,
I'm not convinced by the approach of using a stereo mode flag as a connector property for the simple reason that property has different lifetimes to the associated mode and userspace needs some form of synchronisation in order to be able to switch stereo modes on the fly without corruption. -Chris
On Thu, Sep 13, 2012 at 12:37 PM, Chris Wilson chris@chris-wilson.co.uk wrote:
On Wed, 12 Sep 2012 18:47:12 +0100, Damien Lespiau damien.lespiau@gmail.com wrote:
Hi,
This series introduces stereo 3D modes support and is split in 3 chunks:
- 3 kernel patches to parse the 3D_present flag of the HDMI CEA vendor block, to expose 3D formats flags in modes and to add a new property on connectors supporting stereo 3D,
I'm not convinced by the approach of using a stereo mode flag as a connector property for the simple reason that property has different lifetimes to the associated mode and userspace needs some form of synchronisation in order to be able to switch stereo modes on the fly without corruption.
That's something that makes me feel bit uneasy as well, it seems that the stereo format is really something associated with a mode. Note that the set_property() only stores the new value and it's taken into account the next time a mode is set. I'm not quite sure how to improve on this. The alternatives I can think about would break the current user space.
dri-devel@lists.freedesktop.org