diff options
author | Wu Fengguang <fengguang.wu@intel.com> | 2011-09-05 02:25:34 -0400 |
---|---|---|
committer | Keith Packard <keithp@keithp.com> | 2011-09-21 17:52:41 -0400 |
commit | e0dac65ed45e72fe34cc7ccc76de0ba220bd38bb (patch) | |
tree | f6f1628bb63a9db0b77318e0486e91d26c918ae0 /drivers | |
parent | 76adaa34db407f174dd06370cb60f6029c33b465 (diff) |
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 <yakui.zhao@intel.com>
CC: Wang Zhenyu <zhenyu.z.wang@intel.com>
CC: Jeremy Bush <contractfrombelow@gmail.com>
CC: Christopher White <c.white@pulseforce.com>
CC: Pierre-Louis Bossart <pierre-louis.bossart@intel.com>
CC: Paul Menzel <paulepanter@users.sourceforge.net>
Signed-off-by: Wu Fengguang <fengguang.wu@intel.com>
Signed-off-by: Keith Packard <keithp@keithp.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpu/drm/i915/i915_drv.h | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/i915/i915_reg.h | 25 | ||||
-rw-r--r-- | drivers/gpu/drm/i915/intel_display.c | 131 | ||||
-rw-r--r-- | drivers/gpu/drm/i915/intel_dp.c | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/i915/intel_drv.h | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/i915/intel_hdmi.c | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/i915/intel_modes.c | 2 |
7 files changed, 169 insertions, 2 deletions
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 32de06f0b172..15c0ca58ad8b 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h | |||
@@ -209,6 +209,8 @@ struct drm_i915_display_funcs { | |||
209 | struct drm_display_mode *adjusted_mode, | 209 | struct drm_display_mode *adjusted_mode, |
210 | int x, int y, | 210 | int x, int y, |
211 | struct drm_framebuffer *old_fb); | 211 | struct drm_framebuffer *old_fb); |
212 | void (*write_eld)(struct drm_connector *connector, | ||
213 | struct drm_crtc *crtc); | ||
212 | void (*fdi_link_train)(struct drm_crtc *crtc); | 214 | void (*fdi_link_train)(struct drm_crtc *crtc); |
213 | void (*init_clock_gating)(struct drm_device *dev); | 215 | void (*init_clock_gating)(struct drm_device *dev); |
214 | void (*init_pch_clock_gating)(struct drm_device *dev); | 216 | void (*init_pch_clock_gating)(struct drm_device *dev); |
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 8d9fce1ef0b0..a363fdd54ba2 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h | |||
@@ -3470,4 +3470,29 @@ | |||
3470 | #define GEN6_PCODE_DATA 0x138128 | 3470 | #define GEN6_PCODE_DATA 0x138128 |
3471 | #define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 | 3471 | #define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 |
3472 | 3472 | ||
3473 | #define G4X_AUD_VID_DID 0x62020 | ||
3474 | #define INTEL_AUDIO_DEVCL 0x808629FB | ||
3475 | #define INTEL_AUDIO_DEVBLC 0x80862801 | ||
3476 | #define INTEL_AUDIO_DEVCTG 0x80862802 | ||
3477 | |||
3478 | #define G4X_AUD_CNTL_ST 0x620B4 | ||
3479 | #define G4X_ELDV_DEVCL_DEVBLC (1 << 13) | ||
3480 | #define G4X_ELDV_DEVCTG (1 << 14) | ||
3481 | #define G4X_ELD_ADDR (0xf << 5) | ||
3482 | #define G4X_ELD_ACK (1 << 4) | ||
3483 | #define G4X_HDMIW_HDMIEDID 0x6210C | ||
3484 | |||
3485 | #define GEN5_HDMIW_HDMIEDID_A 0xE2050 | ||
3486 | #define GEN5_AUD_CNTL_ST_A 0xE20B4 | ||
3487 | #define GEN5_ELD_BUFFER_SIZE (0x1f << 10) | ||
3488 | #define GEN5_ELD_ADDRESS (0x1f << 5) | ||
3489 | #define GEN5_ELD_ACK (1 << 4) | ||
3490 | #define GEN5_AUD_CNTL_ST2 0xE20C0 | ||
3491 | #define GEN5_ELD_VALIDB (1 << 0) | ||
3492 | #define GEN5_CP_READYB (1 << 1) | ||
3493 | |||
3494 | #define GEN7_HDMIW_HDMIEDID_A 0xE5050 | ||
3495 | #define GEN7_AUD_CNTRL_ST_A 0xE50B4 | ||
3496 | #define GEN7_AUD_CNTRL_ST2 0xE50C0 | ||
3497 | |||
3473 | #endif /* _I915_REG_H_ */ | 3498 | #endif /* _I915_REG_H_ */ |
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index a685957566c9..f0e5f9f32aa8 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c | |||
@@ -31,6 +31,7 @@ | |||
31 | #include <linux/kernel.h> | 31 | #include <linux/kernel.h> |
32 | #include <linux/slab.h> | 32 | #include <linux/slab.h> |
33 | #include <linux/vgaarb.h> | 33 | #include <linux/vgaarb.h> |
34 | #include <drm/drm_edid.h> | ||
34 | #include "drmP.h" | 35 | #include "drmP.h" |
35 | #include "intel_drv.h" | 36 | #include "intel_drv.h" |
36 | #include "i915_drm.h" | 37 | #include "i915_drm.h" |
@@ -5669,6 +5670,131 @@ static int intel_crtc_mode_set(struct drm_crtc *crtc, | |||
5669 | return ret; | 5670 | return ret; |
5670 | } | 5671 | } |
5671 | 5672 | ||
5673 | static void g4x_write_eld(struct drm_connector *connector, | ||
5674 | struct drm_crtc *crtc) | ||
5675 | { | ||
5676 | struct drm_i915_private *dev_priv = connector->dev->dev_private; | ||
5677 | uint8_t *eld = connector->eld; | ||
5678 | uint32_t eldv; | ||
5679 | uint32_t len; | ||
5680 | uint32_t i; | ||
5681 | |||
5682 | i = I915_READ(G4X_AUD_VID_DID); | ||
5683 | |||
5684 | if (i == INTEL_AUDIO_DEVBLC || i == INTEL_AUDIO_DEVCL) | ||
5685 | eldv = G4X_ELDV_DEVCL_DEVBLC; | ||
5686 | else | ||
5687 | eldv = G4X_ELDV_DEVCTG; | ||
5688 | |||
5689 | i = I915_READ(G4X_AUD_CNTL_ST); | ||
5690 | i &= ~(eldv | G4X_ELD_ADDR); | ||
5691 | len = (i >> 9) & 0x1f; /* ELD buffer size */ | ||
5692 | I915_WRITE(G4X_AUD_CNTL_ST, i); | ||
5693 | |||
5694 | if (!eld[0]) | ||
5695 | return; | ||
5696 | |||
5697 | len = min_t(uint8_t, eld[2], len); | ||
5698 | DRM_DEBUG_DRIVER("ELD size %d\n", len); | ||
5699 | for (i = 0; i < len; i++) | ||
5700 | I915_WRITE(G4X_HDMIW_HDMIEDID, *((uint32_t *)eld + i)); | ||
5701 | |||
5702 | i = I915_READ(G4X_AUD_CNTL_ST); | ||
5703 | i |= eldv; | ||
5704 | I915_WRITE(G4X_AUD_CNTL_ST, i); | ||
5705 | } | ||
5706 | |||
5707 | static void ironlake_write_eld(struct drm_connector *connector, | ||
5708 | struct drm_crtc *crtc) | ||
5709 | { | ||
5710 | struct drm_i915_private *dev_priv = connector->dev->dev_private; | ||
5711 | uint8_t *eld = connector->eld; | ||
5712 | uint32_t eldv; | ||
5713 | uint32_t i; | ||
5714 | int len; | ||
5715 | int hdmiw_hdmiedid; | ||
5716 | int aud_cntl_st; | ||
5717 | int aud_cntrl_st2; | ||
5718 | |||
5719 | if (IS_IVYBRIDGE(connector->dev)) { | ||
5720 | hdmiw_hdmiedid = GEN7_HDMIW_HDMIEDID_A; | ||
5721 | aud_cntl_st = GEN7_AUD_CNTRL_ST_A; | ||
5722 | aud_cntrl_st2 = GEN7_AUD_CNTRL_ST2; | ||
5723 | } else { | ||
5724 | hdmiw_hdmiedid = GEN5_HDMIW_HDMIEDID_A; | ||
5725 | aud_cntl_st = GEN5_AUD_CNTL_ST_A; | ||
5726 | aud_cntrl_st2 = GEN5_AUD_CNTL_ST2; | ||
5727 | } | ||
5728 | |||
5729 | i = to_intel_crtc(crtc)->pipe; | ||
5730 | hdmiw_hdmiedid += i * 0x100; | ||
5731 | aud_cntl_st += i * 0x100; | ||
5732 | |||
5733 | DRM_DEBUG_DRIVER("ELD on pipe %c\n", pipe_name(i)); | ||
5734 | |||
5735 | i = I915_READ(aud_cntl_st); | ||
5736 | i = (i >> 29) & 0x3; /* DIP_Port_Select, 0x1 = PortB */ | ||
5737 | if (!i) { | ||
5738 | DRM_DEBUG_DRIVER("Audio directed to unknown port\n"); | ||
5739 | /* operate blindly on all ports */ | ||
5740 | eldv = GEN5_ELD_VALIDB; | ||
5741 | eldv |= GEN5_ELD_VALIDB << 4; | ||
5742 | eldv |= GEN5_ELD_VALIDB << 8; | ||
5743 | } else { | ||
5744 | DRM_DEBUG_DRIVER("ELD on port %c\n", 'A' + i); | ||
5745 | eldv = GEN5_ELD_VALIDB << ((i - 1) * 4); | ||
5746 | } | ||
5747 | |||
5748 | i = I915_READ(aud_cntrl_st2); | ||
5749 | i &= ~eldv; | ||
5750 | I915_WRITE(aud_cntrl_st2, i); | ||
5751 | |||
5752 | if (!eld[0]) | ||
5753 | return; | ||
5754 | |||
5755 | if (intel_pipe_has_type(crtc, INTEL_OUTPUT_DISPLAYPORT)) { | ||
5756 | DRM_DEBUG_DRIVER("ELD: DisplayPort detected\n"); | ||
5757 | eld[5] |= (1 << 2); /* Conn_Type, 0x1 = DisplayPort */ | ||
5758 | } | ||
5759 | |||
5760 | i = I915_READ(aud_cntl_st); | ||
5761 | i &= ~GEN5_ELD_ADDRESS; | ||
5762 | I915_WRITE(aud_cntl_st, i); | ||
5763 | |||
5764 | len = min_t(uint8_t, eld[2], 21); /* 84 bytes of hw ELD buffer */ | ||
5765 | DRM_DEBUG_DRIVER("ELD size %d\n", len); | ||
5766 | for (i = 0; i < len; i++) | ||
5767 | I915_WRITE(hdmiw_hdmiedid, *((uint32_t *)eld + i)); | ||
5768 | |||
5769 | i = I915_READ(aud_cntrl_st2); | ||
5770 | i |= eldv; | ||
5771 | I915_WRITE(aud_cntrl_st2, i); | ||
5772 | } | ||
5773 | |||
5774 | void intel_write_eld(struct drm_encoder *encoder, | ||
5775 | struct drm_display_mode *mode) | ||
5776 | { | ||
5777 | struct drm_crtc *crtc = encoder->crtc; | ||
5778 | struct drm_connector *connector; | ||
5779 | struct drm_device *dev = encoder->dev; | ||
5780 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
5781 | |||
5782 | connector = drm_select_eld(encoder, mode); | ||
5783 | if (!connector) | ||
5784 | return; | ||
5785 | |||
5786 | DRM_DEBUG_DRIVER("ELD on [CONNECTOR:%d:%s], [ENCODER:%d:%s]\n", | ||
5787 | connector->base.id, | ||
5788 | drm_get_connector_name(connector), | ||
5789 | connector->encoder->base.id, | ||
5790 | drm_get_encoder_name(connector->encoder)); | ||
5791 | |||
5792 | connector->eld[6] = drm_av_sync_delay(connector, mode) / 2; | ||
5793 | |||
5794 | if (dev_priv->display.write_eld) | ||
5795 | dev_priv->display.write_eld(connector, crtc); | ||
5796 | } | ||
5797 | |||
5672 | /** Loads the palette/gamma unit for the CRTC with the prepared values */ | 5798 | /** Loads the palette/gamma unit for the CRTC with the prepared values */ |
5673 | void intel_crtc_load_lut(struct drm_crtc *crtc) | 5799 | void intel_crtc_load_lut(struct drm_crtc *crtc) |
5674 | { | 5800 | { |
@@ -8185,6 +8311,7 @@ static void intel_init_display(struct drm_device *dev) | |||
8185 | } | 8311 | } |
8186 | dev_priv->display.fdi_link_train = ironlake_fdi_link_train; | 8312 | dev_priv->display.fdi_link_train = ironlake_fdi_link_train; |
8187 | dev_priv->display.init_clock_gating = ironlake_init_clock_gating; | 8313 | dev_priv->display.init_clock_gating = ironlake_init_clock_gating; |
8314 | dev_priv->display.write_eld = ironlake_write_eld; | ||
8188 | } else if (IS_GEN6(dev)) { | 8315 | } else if (IS_GEN6(dev)) { |
8189 | if (SNB_READ_WM0_LATENCY()) { | 8316 | if (SNB_READ_WM0_LATENCY()) { |
8190 | dev_priv->display.update_wm = sandybridge_update_wm; | 8317 | dev_priv->display.update_wm = sandybridge_update_wm; |
@@ -8195,6 +8322,7 @@ static void intel_init_display(struct drm_device *dev) | |||
8195 | } | 8322 | } |
8196 | dev_priv->display.fdi_link_train = gen6_fdi_link_train; | 8323 | dev_priv->display.fdi_link_train = gen6_fdi_link_train; |
8197 | dev_priv->display.init_clock_gating = gen6_init_clock_gating; | 8324 | dev_priv->display.init_clock_gating = gen6_init_clock_gating; |
8325 | dev_priv->display.write_eld = ironlake_write_eld; | ||
8198 | } else if (IS_IVYBRIDGE(dev)) { | 8326 | } else if (IS_IVYBRIDGE(dev)) { |
8199 | /* FIXME: detect B0+ stepping and use auto training */ | 8327 | /* FIXME: detect B0+ stepping and use auto training */ |
8200 | dev_priv->display.fdi_link_train = ivb_manual_fdi_link_train; | 8328 | dev_priv->display.fdi_link_train = ivb_manual_fdi_link_train; |
@@ -8206,7 +8334,7 @@ static void intel_init_display(struct drm_device *dev) | |||
8206 | dev_priv->display.update_wm = NULL; | 8334 | dev_priv->display.update_wm = NULL; |
8207 | } | 8335 | } |
8208 | dev_priv->display.init_clock_gating = ivybridge_init_clock_gating; | 8336 | dev_priv->display.init_clock_gating = ivybridge_init_clock_gating; |
8209 | 8337 | dev_priv->display.write_eld = ironlake_write_eld; | |
8210 | } else | 8338 | } else |
8211 | dev_priv->display.update_wm = NULL; | 8339 | dev_priv->display.update_wm = NULL; |
8212 | } else if (IS_PINEVIEW(dev)) { | 8340 | } else if (IS_PINEVIEW(dev)) { |
@@ -8226,6 +8354,7 @@ static void intel_init_display(struct drm_device *dev) | |||
8226 | dev_priv->display.update_wm = pineview_update_wm; | 8354 | dev_priv->display.update_wm = pineview_update_wm; |
8227 | dev_priv->display.init_clock_gating = gen3_init_clock_gating; | 8355 | dev_priv->display.init_clock_gating = gen3_init_clock_gating; |
8228 | } else if (IS_G4X(dev)) { | 8356 | } else if (IS_G4X(dev)) { |
8357 | dev_priv->display.write_eld = g4x_write_eld; | ||
8229 | dev_priv->display.update_wm = g4x_update_wm; | 8358 | dev_priv->display.update_wm = g4x_update_wm; |
8230 | dev_priv->display.init_clock_gating = g4x_init_clock_gating; | 8359 | dev_priv->display.init_clock_gating = g4x_init_clock_gating; |
8231 | } else if (IS_GEN4(dev)) { | 8360 | } else if (IS_GEN4(dev)) { |
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 4091f2182e6d..6cbde9ff1ec6 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c | |||
@@ -773,8 +773,12 @@ intel_dp_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, | |||
773 | intel_dp->DP |= DP_PORT_WIDTH_4; | 773 | intel_dp->DP |= DP_PORT_WIDTH_4; |
774 | break; | 774 | break; |
775 | } | 775 | } |
776 | if (intel_dp->has_audio) | 776 | if (intel_dp->has_audio) { |
777 | DRM_DEBUG_DRIVER("Enabling DP audio on pipe %c\n", | ||
778 | pipe_name(intel_crtc->pipe)); | ||
777 | intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE; | 779 | intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE; |
780 | intel_write_eld(encoder, adjusted_mode); | ||
781 | } | ||
778 | 782 | ||
779 | memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE); | 783 | memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE); |
780 | intel_dp->link_configuration[0] = intel_dp->link_bw; | 784 | intel_dp->link_configuration[0] = intel_dp->link_bw; |
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 375690b21624..b7e718639b13 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h | |||
@@ -380,4 +380,6 @@ extern void intel_fb_output_poll_changed(struct drm_device *dev); | |||
380 | extern void intel_fb_restore_mode(struct drm_device *dev); | 380 | extern void intel_fb_restore_mode(struct drm_device *dev); |
381 | 381 | ||
382 | extern void intel_init_clock_gating(struct drm_device *dev); | 382 | extern void intel_init_clock_gating(struct drm_device *dev); |
383 | extern void intel_write_eld(struct drm_encoder *encoder, | ||
384 | struct drm_display_mode *mode); | ||
383 | #endif /* __INTEL_DRV_H__ */ | 385 | #endif /* __INTEL_DRV_H__ */ |
diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 226ba830f383..75026ba41a8e 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c | |||
@@ -245,8 +245,11 @@ static void intel_hdmi_mode_set(struct drm_encoder *encoder, | |||
245 | sdvox |= HDMI_MODE_SELECT; | 245 | sdvox |= HDMI_MODE_SELECT; |
246 | 246 | ||
247 | if (intel_hdmi->has_audio) { | 247 | if (intel_hdmi->has_audio) { |
248 | DRM_DEBUG_DRIVER("Enabling HDMI audio on pipe %c\n", | ||
249 | pipe_name(intel_crtc->pipe)); | ||
248 | sdvox |= SDVO_AUDIO_ENABLE; | 250 | sdvox |= SDVO_AUDIO_ENABLE; |
249 | sdvox |= SDVO_NULL_PACKETS_DURING_VSYNC; | 251 | sdvox |= SDVO_NULL_PACKETS_DURING_VSYNC; |
252 | intel_write_eld(encoder, adjusted_mode); | ||
250 | } | 253 | } |
251 | 254 | ||
252 | if (intel_crtc->pipe == 1) { | 255 | if (intel_crtc->pipe == 1) { |
diff --git a/drivers/gpu/drm/i915/intel_modes.c b/drivers/gpu/drm/i915/intel_modes.c index 3b26a3ba02dd..be2c6fe07d12 100644 --- a/drivers/gpu/drm/i915/intel_modes.c +++ b/drivers/gpu/drm/i915/intel_modes.c | |||
@@ -26,6 +26,7 @@ | |||
26 | #include <linux/slab.h> | 26 | #include <linux/slab.h> |
27 | #include <linux/i2c.h> | 27 | #include <linux/i2c.h> |
28 | #include <linux/fb.h> | 28 | #include <linux/fb.h> |
29 | #include <drm/drm_edid.h> | ||
29 | #include "drmP.h" | 30 | #include "drmP.h" |
30 | #include "intel_drv.h" | 31 | #include "intel_drv.h" |
31 | #include "i915_drv.h" | 32 | #include "i915_drv.h" |
@@ -74,6 +75,7 @@ int intel_ddc_get_modes(struct drm_connector *connector, | |||
74 | if (edid) { | 75 | if (edid) { |
75 | drm_mode_connector_update_edid_property(connector, edid); | 76 | drm_mode_connector_update_edid_property(connector, edid); |
76 | ret = drm_add_edid_modes(connector, edid); | 77 | ret = drm_add_edid_modes(connector, edid); |
78 | drm_edid_to_eld(connector, edid); | ||
77 | connector->display_info.raw_edid = NULL; | 79 | connector->display_info.raw_edid = NULL; |
78 | kfree(edid); | 80 | kfree(edid); |
79 | } | 81 | } |