Skip to contentSkip to conversation

drm: support routines for HDMI/DP ELD

PATCH In review Wu Fengguang 1 participant 4 messages Sep 5, 2011

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/2 drm: support routines for HDMI/DP ELD

ELD (EDID-Like Data) describes to the HDMI/DP audio driver the audio capabilities of the plugged monitor.

This adds drm_edid_to_eld() for converting EDID to ELD. The converted ELD will be saved in a new drm_connector.eld[128] data field. This is necessary because the graphics driver will need to fixup some of the data fields (eg. HDMI/DP connection type, AV sync delay) before writing to the hardware ELD buffer. drm_av_sync_delay() will help the graphics drivers dynamically compute the AV sync delay for fixing-up the ELD.

ELD selection policy: it's possible for one encoder to be associated with multiple connectors (ie. monitors), in which case the first found ELD will be returned by drm_select_eld(). This policy may not be suitable for all users, but let's start it simple first.

The impact of ELD selection policy: assume there are two monitors, one supports stereo playback and the other has 8-channel output; cloned display mode is used, so that the two monitors are associated with the same internal encoder. If only the stereo playback capability is reported, the user won't be able to start 8-channel playback; if the 8-channel ELD is reported, then user space applications may send 8-channel samples down, however the user may actually be listening to the 2-channel monitor and not connecting speakers to the 8-channel monitor.

According to James, many TVs will either refuse the display anything or pop-up an OSD warning whenever they receive hdmi audio which they cannot handle. Eventually we will require configurability and/or per-monitor audio control even when the video is cloned.

CC Zhao Yakui <[email protected]>
CC Wang Zhenyu <[email protected]>
CC Jeremy Bush <[email protected]>
CC Christopher White <[email protected]>
CC Pierre-Louis Bossart <[email protected]>
CC Paul Menzel <[email protected]>
CC James Cloos <[email protected]>
CC Chris Wilson <[email protected]>
---
 drivers/gpu/drm/drm_edid.c |  171 +++++++++++++++++++++++++++++++++++
 include/drm/drm_edid.h     |    9 +
 2 files changed, 180 insertions(+)
include/drm/drm_crtc.h +9 -0
@@ -466,6 +466,8 @@ enum drm_connector_force {
 /* DACs should rarely do this without a lot of testing */
 #define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2)
 
+#define MAX_ELD_BYTES	128
+
 /**
  * drm_connector - central DRM connector control structure
  * @crtc: CRTC this connector is currently connected to, NULL if none
@@ -523,6 +525,13 @@ struct drm_connector {
 	uint32_t force_encoder_id;
 	struct drm_encoder *encoder; /* currently active encoder */
 
+	/* EDID bits */
+	uint8_t eld[MAX_ELD_BYTES];
+	bool dvi_dual;
+	int max_tmds_clock;	/* in MHz */
+	bool latency_present[2];
+	int video_latency[2];	/* [0]: progressive, [1]: interlaced */
+	int audio_latency[2];
 	int null_edid_counter; /* needed to workaround some HW bugs where we get all 0s */
 };
 
include/drm/drm_edid.h +9 -0
@@ -230,4 +230,13 @@ struct edid {
 
 #define EDID_PRODUCT_ID(e) ((e)->prod_code[0] | ((e)->prod_code[1] << 8))
 
+struct drm_encoder;
+struct drm_connector;
+struct drm_display_mode;
+void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid);
+int drm_av_sync_delay(struct drm_connector *connector,
+		      struct drm_display_mode *mode);
+struct drm_connector *drm_select_eld(struct drm_encoder *encoder,
+				     struct drm_display_mode *mode);
+
 #endif /* __DRM_EDID_H__ */
drivers/gpu/drm/drm_edid.c +171 -0

183 lines not on this page · raw message

#1 lore raw

PATCH 2/2 drm/i915: pass ELD to HDMI/DP audio driver

Add ELD support for Intel Eaglelake, IbexPeak/Ironlake, SandyBridge/CougarPoint and IvyBridge/PantherPoint chips.

ELD (EDID-Like Data) describes to the HDMI/DP audio driver the audio capabilities of the plugged monitor. It's built and passed to audio driver in 2 steps:

(1) at get_modes time, parse EDID and save ELD to drm_connector.eld[]

(2) at mode_set time, write drm_connector.eld[] to the Transcoder's hw ELD buffer and set the ELD_valid bit to inform HDMI/DP audio driver

This patch is tested OK on G45/HDMI, IbexPeak/HDMI and IvyBridge/HDMI+DP. Test scheme: plug in the HDMI/DP monitor, and run

cat /proc/asound/card0/eld*

to check if the monitor name, HDMI/DP type, etc. show up correctly.

Minor imperfection: the GEN5_AUD_CNTL_ST/DIP_Port_Select field always reads 0 (reserved). Without knowing the port number, I worked it around by setting the ELD_valid bit for ALL the three ports. It's tested to not be a problem, because the audio driver will find invalid ELD data and hence rightfully abort, even when it sees the ELD_valid indicator.

Thanks to Zhenyu and Pierre-Louis for a lot of valuable help and testing.

CC Zhao Yakui <[email protected]>
CC Wang Zhenyu <[email protected]>
CC Jeremy Bush <[email protected]>
CC Christopher White <[email protected]>
CC Pierre-Louis Bossart <[email protected]>
CC Paul Menzel <[email protected]>
---
 drivers/gpu/drm/i915/i915_drv.h      |    2 
 drivers/gpu/drm/i915/i915_reg.h      |   25 ++++
 drivers/gpu/drm/i915/intel_display.c |  131 ++++++++++++++++++++++++-
 drivers/gpu/drm/i915/intel_dp.c      |    6 -
 drivers/gpu/drm/i915/intel_drv.h     |    2 
 drivers/gpu/drm/i915/intel_hdmi.c    |    3 
 drivers/gpu/drm/i915/intel_modes.c   |    2 
 7 files changed, 169 insertions(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_hdmi.c +3 -0
@@ -245,8 +245,11 @@ static void intel_hdmi_mode_set(struct d
 		sdvox |= HDMI_MODE_SELECT;
 
 	if (intel_hdmi->has_audio) {
+		DRM_DEBUG_DRIVER("Enabling HDMI audio on pipe %c\n",
+				 pipe_name(intel_crtc->pipe));
 		sdvox |= SDVO_AUDIO_ENABLE;
 		sdvox |= SDVO_NULL_PACKETS_DURING_VSYNC;
+		intel_write_eld(encoder, adjusted_mode);
 	}
 
 	if (intel_crtc->pipe == 1) {
drivers/gpu/drm/i915/intel_display.c +130 -1

167 lines not on this page · raw message

drivers/gpu/drm/i915/i915_reg.h +25 -0
@@ -3470,4 +3470,29 @@
 #define GEN6_PCODE_DATA				0x138128
 #define   GEN6_PCODE_FREQ_IA_RATIO_SHIFT	8
 
+#define G4X_AUD_VID_DID			0x62020
+#define INTEL_AUDIO_DEVCL		0x808629FB
+#define INTEL_AUDIO_DEVBLC		0x80862801
+#define INTEL_AUDIO_DEVCTG		0x80862802
+
+#define G4X_AUD_CNTL_ST			0x620B4
+#define G4X_ELDV_DEVCL_DEVBLC		(1 << 13)
+#define G4X_ELDV_DEVCTG			(1 << 14)
+#define G4X_ELD_ADDR			(0xf << 5)
+#define G4X_ELD_ACK			(1 << 4)
+#define G4X_HDMIW_HDMIEDID		0x6210C
+
+#define GEN5_HDMIW_HDMIEDID_A		0xE2050
+#define GEN5_AUD_CNTL_ST_A		0xE20B4
+#define GEN5_ELD_BUFFER_SIZE		(0x1f << 10)
+#define GEN5_ELD_ADDRESS		(0x1f << 5)
+#define GEN5_ELD_ACK			(1 << 4)
+#define GEN5_AUD_CNTL_ST2		0xE20C0
+#define GEN5_ELD_VALIDB			(1 << 0)
+#define GEN5_CP_READYB			(1 << 1)
+
+#define GEN7_HDMIW_HDMIEDID_A		0xE5050
+#define GEN7_AUD_CNTRL_ST_A		0xE50B4
+#define GEN7_AUD_CNTRL_ST2		0xE50C0
+
 #endif /* _I915_REG_H_ */
drivers/gpu/drm/i915/intel_drv.h +2 -0
@@ -380,4 +380,6 @@ extern void intel_fb_output_poll_changed
 extern void intel_fb_restore_mode(struct drm_device *dev);
 
 extern void intel_init_clock_gating(struct drm_device *dev);
+extern void intel_write_eld(struct drm_encoder *encoder,
+			    struct drm_display_mode *mode);
 #endif /* __INTEL_DRV_H__ */
drivers/gpu/drm/i915/intel_modes.c +2 -0
@@ -26,6 +26,7 @@
 #include <linux/slab.h>
 #include <linux/i2c.h>
 #include <linux/fb.h>
+#include <drm/drm_edid.h>
 #include "drmP.h"
 #include "intel_drv.h"
 #include "i915_drv.h"
@@ -74,6 +75,7 @@ int intel_ddc_get_modes(struct drm_conne
 	if (edid) {
 		drm_mode_connector_update_edid_property(connector, edid);
 		ret = drm_add_edid_modes(connector, edid);
+		drm_edid_to_eld(connector, edid);
 		connector->display_info.raw_edid = NULL;
 		kfree(edid);
 	}
drivers/gpu/drm/i915/intel_dp.c +5 -1
@@ -773,8 +773,12 @@ intel_dp_mode_set(struct drm_encoder *en
 		intel_dp->DP |= DP_PORT_WIDTH_4;
 		break;
 	}
-	if (intel_dp->has_audio)
+	if (intel_dp->has_audio) {
+		DRM_DEBUG_DRIVER("Enabling DP audio on pipe %c\n",
+				 pipe_name(intel_crtc->pipe));
 		intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE;
+		intel_write_eld(encoder, adjusted_mode);
+	}
 
 	memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE);
 	intel_dp->link_configuration[0] = intel_dp->link_bw;
drivers/gpu/drm/i915/i915_drv.h +2 -0
@@ -209,6 +209,8 @@ struct drm_i915_display_funcs {
 			     struct drm_display_mode *adjusted_mode,
 			     int x, int y,
 			     struct drm_framebuffer *old_fb);
+	void (*write_eld)(struct drm_connector *connector,
+			  struct drm_crtc *crtc);
 	void (*fdi_link_train)(struct drm_crtc *crtc);
 	void (*init_clock_gating)(struct drm_device *dev);
 	void (*init_pch_clock_gating)(struct drm_device *dev);
#2 lore raw

Re: [PATCH 2/2] drm/i915: pass ELD to HDMI/DP audio driver

I'd like to do more cleanups:

drivers/gpu/drm/i915/intel_display.c static int intel_crtc_mode_set(struct dr · line 5715

Quoted 2 lines
drivers/gpu/drm/i915/intel_display.c +2 -0
+	int aud_cntl_st;
+	int aud_cntrl_st2;

s/aud_cntrl_st2/aud_cntl_st2/

Quoted 15 lines
drivers/gpu/drm/i915/intel_display.c +15 -0
+	if (IS_IVYBRIDGE(connector->dev)) {
+		hdmiw_hdmiedid = GEN7_HDMIW_HDMIEDID_A;
+		aud_cntl_st = GEN7_AUD_CNTRL_ST_A;
+		aud_cntrl_st2 = GEN7_AUD_CNTRL_ST2;
+	} else {
+		hdmiw_hdmiedid = GEN5_HDMIW_HDMIEDID_A;
+		aud_cntl_st = GEN5_AUD_CNTL_ST_A;
+		aud_cntrl_st2 = GEN5_AUD_CNTL_ST2;
+	}
+
+	i = to_intel_crtc(crtc)->pipe;
+	hdmiw_hdmiedid += i * 0x100;
+	aud_cntl_st += i * 0x100;
+
+	DRM_DEBUG_DRIVER("ELD on pipe %c\n", pipe_name(i));

[...]

drivers/gpu/drm/i915/intel_display.c static int intel_crtc_mode_set(struct dr · line 5763

Quoted 2 lines
drivers/gpu/drm/i915/intel_display.c +2 -0
+	len = min_t(uint8_t, eld[2], 21);	/* 84 bytes of hw ELD buffer */
+	DRM_DEBUG_DRIVER("ELD size %d\n", len);

Merge the above two DRM_DEBUG_DRIVER() calls into:

+ DRM_DEBUG_DRIVER("writing %d ELD dword(s) on pipe %c\n",
+ len, pipe_name(to_intel_crtc(crtc)->pipe));

drivers/gpu/drm/i915/intel_display.c static int intel_crtc_mode_set(struct dr · line 5765

Quoted 2 lines
drivers/gpu/drm/i915/intel_display.c +2 -0
+	for (i = 0; i < len; i++)
+		I915_WRITE(hdmiw_hdmiedid, *((uint32_t *)eld + i));

Thanks,
Fengguang

#3 lore raw

PATCH v2 2/2 drm/i915: pass ELD to HDMI/DP audio driver

Add ELD support for Intel Eaglelake, IbexPeak/Ironlake, SandyBridge/CougarPoint and IvyBridge/PantherPoint chips.

ELD (EDID-Like Data) describes to the HDMI/DP audio driver the audio capabilities of the plugged monitor. It's built and passed to audio driver in 2 steps:

(1) at get_modes time, parse EDID and save ELD to drm_connector.eld[]

(2) at mode_set time, write drm_connector.eld[] to the Transcoder's hw ELD buffer and set the ELD_valid bit to inform HDMI/DP audio driver

This patch is tested OK on G45/HDMI, IbexPeak/HDMI and IvyBridge/HDMI+DP. Test scheme: plug in the HDMI/DP monitor, and run

cat /proc/asound/card0/eld*

to check if the monitor name, HDMI/DP type, etc. show up correctly.

Minor imperfection: the GEN5_AUD_CNTL_ST/DIP_Port_Select field always reads 0 (reserved). Without knowing the port number, I worked it around by setting the ELD_valid bit for ALL the three ports. It's tested to not be a problem, because the audio driver will either find unpresent monitor or invalid ELD data and abort rightfully even when it sees the ELD_valid indicator.

Thanks to Zhenyu and Pierre-Louis for a lot of valuable help and testing.

CC Zhao Yakui <[email protected]>
CC Wang Zhenyu <[email protected]>
CC Jeremy Bush <[email protected]>
CC Christopher White <[email protected]>
CC Paul Menzel <[email protected]>
CC Pierre-Louis Bossart <[email protected]>
---
 drivers/gpu/drm/i915/i915_drv.h      |    2 
 drivers/gpu/drm/i915/i915_reg.h      |   25 ++++
 drivers/gpu/drm/i915/intel_display.c |  131 ++++++++++++++++++++++++-
 drivers/gpu/drm/i915/intel_dp.c      |    6 -
 drivers/gpu/drm/i915/intel_drv.h     |    2 
 drivers/gpu/drm/i915/intel_hdmi.c    |    3 
 drivers/gpu/drm/i915/intel_modes.c   |    2 
 7 files changed, 169 insertions(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_hdmi.c +3 -0
@@ -245,8 +245,11 @@ static void intel_hdmi_mode_set(struct d
 		sdvox |= HDMI_MODE_SELECT;
 
 	if (intel_hdmi->has_audio) {
+		DRM_DEBUG_DRIVER("Enabling HDMI audio on pipe %c\n",
+				 pipe_name(intel_crtc->pipe));
 		sdvox |= SDVO_AUDIO_ENABLE;
 		sdvox |= SDVO_NULL_PACKETS_DURING_VSYNC;
+		intel_write_eld(encoder, adjusted_mode);
 	}
 
 	if (intel_crtc->pipe == 1) {
drivers/gpu/drm/i915/intel_display.c +130 -1

167 lines not on this page · raw message

drivers/gpu/drm/i915/i915_reg.h +25 -0
@@ -3470,4 +3470,29 @@
 #define GEN6_PCODE_DATA				0x138128
 #define   GEN6_PCODE_FREQ_IA_RATIO_SHIFT	8
 
+#define G4X_AUD_VID_DID			0x62020
+#define INTEL_AUDIO_DEVCL		0x808629FB
+#define INTEL_AUDIO_DEVBLC		0x80862801
+#define INTEL_AUDIO_DEVCTG		0x80862802
+
+#define G4X_AUD_CNTL_ST			0x620B4
+#define G4X_ELDV_DEVCL_DEVBLC		(1 << 13)
+#define G4X_ELDV_DEVCTG			(1 << 14)
+#define G4X_ELD_ADDR			(0xf << 5)
+#define G4X_ELD_ACK			(1 << 4)
+#define G4X_HDMIW_HDMIEDID		0x6210C
+
+#define GEN5_HDMIW_HDMIEDID_A		0xE2050
+#define GEN5_AUD_CNTL_ST_A		0xE20B4
+#define GEN5_ELD_BUFFER_SIZE		(0x1f << 10)
+#define GEN5_ELD_ADDRESS		(0x1f << 5)
+#define GEN5_ELD_ACK			(1 << 4)
+#define GEN5_AUD_CNTL_ST2		0xE20C0
+#define GEN5_ELD_VALIDB			(1 << 0)
+#define GEN5_CP_READYB			(1 << 1)
+
+#define GEN7_HDMIW_HDMIEDID_A		0xE5050
+#define GEN7_AUD_CNTRL_ST_A		0xE50B4
+#define GEN7_AUD_CNTRL_ST2		0xE50C0
+
 #endif /* _I915_REG_H_ */
drivers/gpu/drm/i915/intel_drv.h +2 -0
@@ -380,4 +380,6 @@ extern void intel_fb_output_poll_changed
 extern void intel_fb_restore_mode(struct drm_device *dev);
 
 extern void intel_init_clock_gating(struct drm_device *dev);
+extern void intel_write_eld(struct drm_encoder *encoder,
+			    struct drm_display_mode *mode);
 #endif /* __INTEL_DRV_H__ */
drivers/gpu/drm/i915/intel_modes.c +2 -0
@@ -26,6 +26,7 @@
 #include <linux/slab.h>
 #include <linux/i2c.h>
 #include <linux/fb.h>
+#include <drm/drm_edid.h>
 #include "drmP.h"
 #include "intel_drv.h"
 #include "i915_drv.h"
@@ -74,6 +75,7 @@ int intel_ddc_get_modes(struct drm_conne
 	if (edid) {
 		drm_mode_connector_update_edid_property(connector, edid);
 		ret = drm_add_edid_modes(connector, edid);
+		drm_edid_to_eld(connector, edid);
 		connector->display_info.raw_edid = NULL;
 		kfree(edid);
 	}
drivers/gpu/drm/i915/intel_dp.c +5 -1
@@ -773,8 +773,12 @@ intel_dp_mode_set(struct drm_encoder *en
 		intel_dp->DP |= DP_PORT_WIDTH_4;
 		break;
 	}
-	if (intel_dp->has_audio)
+	if (intel_dp->has_audio) {
+		DRM_DEBUG_DRIVER("Enabling DP audio on pipe %c\n",
+				 pipe_name(intel_crtc->pipe));
 		intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE;
+		intel_write_eld(encoder, adjusted_mode);
+	}
 
 	memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE);
 	intel_dp->link_configuration[0] = intel_dp->link_bw;
drivers/gpu/drm/i915/i915_drv.h +2 -0
@@ -209,6 +209,8 @@ struct drm_i915_display_funcs {
 			     struct drm_display_mode *adjusted_mode,
 			     int x, int y,
 			     struct drm_framebuffer *old_fb);
+	void (*write_eld)(struct drm_connector *connector,
+			  struct drm_crtc *crtc);
 	void (*fdi_link_train)(struct drm_crtc *crtc);
 	void (*init_clock_gating)(struct drm_device *dev);
 	void (*init_pch_clock_gating)(struct drm_device *dev);