diff options
author | Eric Anholt <eric@anholt.net> | 2009-01-02 16:33:00 -0500 |
---|---|---|
committer | Dave Airlie <airlied@linux.ie> | 2009-01-06 20:49:47 -0500 |
commit | 7d57382e65994ab7d01741373bd1c420370aed9f (patch) | |
tree | b0c3c5f9657a360db60c45b4c4091b7c027a637f /drivers/gpu/drm/i915/intel_hdmi.c | |
parent | 3f8bc370ac679a5fe5c098f30d3cf8e80f62a9f8 (diff) |
drm/i915: Add support for integrated HDMI on G4X hardware.
This is ported directly from the userland 2D driver code. The HDMI audio bits
aren't hooked up yet.
Signed-off-by: Eric Anholt <eric@anholt.net>
Signed-off-by: Dave Airlie <airlied@linux.ie>
Diffstat (limited to 'drivers/gpu/drm/i915/intel_hdmi.c')
-rw-r--r-- | drivers/gpu/drm/i915/intel_hdmi.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c new file mode 100644 index 000000000000..b06a4a3ff08d --- /dev/null +++ b/drivers/gpu/drm/i915/intel_hdmi.c | |||
@@ -0,0 +1,280 @@ | |||
1 | /* | ||
2 | * Copyright 2006 Dave Airlie <airlied@linux.ie> | ||
3 | * Copyright © 2006-2009 Intel Corporation | ||
4 | * | ||
5 | * Permission is hereby granted, free of charge, to any person obtaining a | ||
6 | * copy of this software and associated documentation files (the "Software"), | ||
7 | * to deal in the Software without restriction, including without limitation | ||
8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
9 | * and/or sell copies of the Software, and to permit persons to whom the | ||
10 | * Software is furnished to do so, subject to the following conditions: | ||
11 | * | ||
12 | * The above copyright notice and this permission notice (including the next | ||
13 | * paragraph) shall be included in all copies or substantial portions of the | ||
14 | * Software. | ||
15 | * | ||
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
22 | * DEALINGS IN THE SOFTWARE. | ||
23 | * | ||
24 | * Authors: | ||
25 | * Eric Anholt <eric@anholt.net> | ||
26 | * Jesse Barnes <jesse.barnes@intel.com> | ||
27 | */ | ||
28 | |||
29 | #include <linux/i2c.h> | ||
30 | #include <linux/delay.h> | ||
31 | #include "drmP.h" | ||
32 | #include "drm.h" | ||
33 | #include "drm_crtc.h" | ||
34 | #include "intel_drv.h" | ||
35 | #include "i915_drm.h" | ||
36 | #include "i915_drv.h" | ||
37 | |||
38 | struct intel_hdmi_priv { | ||
39 | u32 sdvox_reg; | ||
40 | u32 save_SDVOX; | ||
41 | int has_hdmi_sink; | ||
42 | }; | ||
43 | |||
44 | static void intel_hdmi_mode_set(struct drm_encoder *encoder, | ||
45 | struct drm_display_mode *mode, | ||
46 | struct drm_display_mode *adjusted_mode) | ||
47 | { | ||
48 | struct drm_device *dev = encoder->dev; | ||
49 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
50 | struct drm_crtc *crtc = encoder->crtc; | ||
51 | struct intel_crtc *intel_crtc = to_intel_crtc(crtc); | ||
52 | struct intel_output *intel_output = enc_to_intel_output(encoder); | ||
53 | struct intel_hdmi_priv *hdmi_priv = intel_output->dev_priv; | ||
54 | u32 sdvox; | ||
55 | |||
56 | sdvox = SDVO_ENCODING_HDMI | | ||
57 | SDVO_BORDER_ENABLE | | ||
58 | SDVO_VSYNC_ACTIVE_HIGH | | ||
59 | SDVO_HSYNC_ACTIVE_HIGH; | ||
60 | |||
61 | if (hdmi_priv->has_hdmi_sink) | ||
62 | sdvox |= SDVO_AUDIO_ENABLE; | ||
63 | |||
64 | if (intel_crtc->pipe == 1) | ||
65 | sdvox |= SDVO_PIPE_B_SELECT; | ||
66 | |||
67 | I915_WRITE(hdmi_priv->sdvox_reg, sdvox); | ||
68 | POSTING_READ(hdmi_priv->sdvox_reg); | ||
69 | } | ||
70 | |||
71 | static void intel_hdmi_dpms(struct drm_encoder *encoder, int mode) | ||
72 | { | ||
73 | struct drm_device *dev = encoder->dev; | ||
74 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
75 | struct intel_output *intel_output = enc_to_intel_output(encoder); | ||
76 | struct intel_hdmi_priv *hdmi_priv = intel_output->dev_priv; | ||
77 | u32 temp; | ||
78 | |||
79 | if (mode != DRM_MODE_DPMS_ON) { | ||
80 | temp = I915_READ(hdmi_priv->sdvox_reg); | ||
81 | I915_WRITE(hdmi_priv->sdvox_reg, temp & ~SDVO_ENABLE); | ||
82 | } else { | ||
83 | temp = I915_READ(hdmi_priv->sdvox_reg); | ||
84 | I915_WRITE(hdmi_priv->sdvox_reg, temp | SDVO_ENABLE); | ||
85 | } | ||
86 | POSTING_READ(hdmi_priv->sdvox_reg); | ||
87 | } | ||
88 | |||
89 | static void intel_hdmi_save(struct drm_connector *connector) | ||
90 | { | ||
91 | struct drm_device *dev = connector->dev; | ||
92 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
93 | struct intel_output *intel_output = to_intel_output(connector); | ||
94 | struct intel_hdmi_priv *hdmi_priv = intel_output->dev_priv; | ||
95 | |||
96 | hdmi_priv->save_SDVOX = I915_READ(hdmi_priv->sdvox_reg); | ||
97 | } | ||
98 | |||
99 | static void intel_hdmi_restore(struct drm_connector *connector) | ||
100 | { | ||
101 | struct drm_device *dev = connector->dev; | ||
102 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
103 | struct intel_output *intel_output = to_intel_output(connector); | ||
104 | struct intel_hdmi_priv *hdmi_priv = intel_output->dev_priv; | ||
105 | |||
106 | I915_WRITE(hdmi_priv->sdvox_reg, hdmi_priv->save_SDVOX); | ||
107 | POSTING_READ(hdmi_priv->sdvox_reg); | ||
108 | } | ||
109 | |||
110 | static int intel_hdmi_mode_valid(struct drm_connector *connector, | ||
111 | struct drm_display_mode *mode) | ||
112 | { | ||
113 | if (mode->clock > 165000) | ||
114 | return MODE_CLOCK_HIGH; | ||
115 | if (mode->clock < 20000) | ||
116 | return MODE_CLOCK_HIGH; | ||
117 | |||
118 | if (mode->flags & DRM_MODE_FLAG_DBLSCAN) | ||
119 | return MODE_NO_DBLESCAN; | ||
120 | |||
121 | return MODE_OK; | ||
122 | } | ||
123 | |||
124 | static bool intel_hdmi_mode_fixup(struct drm_encoder *encoder, | ||
125 | struct drm_display_mode *mode, | ||
126 | struct drm_display_mode *adjusted_mode) | ||
127 | { | ||
128 | return true; | ||
129 | } | ||
130 | |||
131 | static enum drm_connector_status | ||
132 | intel_hdmi_detect(struct drm_connector *connector) | ||
133 | { | ||
134 | struct drm_device *dev = connector->dev; | ||
135 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
136 | struct intel_output *intel_output = to_intel_output(connector); | ||
137 | struct intel_hdmi_priv *hdmi_priv = intel_output->dev_priv; | ||
138 | u32 temp, bit; | ||
139 | |||
140 | temp = I915_READ(PORT_HOTPLUG_EN); | ||
141 | |||
142 | I915_WRITE(PORT_HOTPLUG_EN, | ||
143 | temp | | ||
144 | HDMIB_HOTPLUG_INT_EN | | ||
145 | HDMIC_HOTPLUG_INT_EN | | ||
146 | HDMID_HOTPLUG_INT_EN); | ||
147 | |||
148 | POSTING_READ(PORT_HOTPLUG_EN); | ||
149 | |||
150 | switch (hdmi_priv->sdvox_reg) { | ||
151 | case SDVOB: | ||
152 | bit = HDMIB_HOTPLUG_INT_STATUS; | ||
153 | break; | ||
154 | case SDVOC: | ||
155 | bit = HDMIC_HOTPLUG_INT_STATUS; | ||
156 | break; | ||
157 | default: | ||
158 | return connector_status_unknown; | ||
159 | } | ||
160 | |||
161 | if ((I915_READ(PORT_HOTPLUG_STAT) & bit) != 0) | ||
162 | return connector_status_connected; | ||
163 | else | ||
164 | return connector_status_disconnected; | ||
165 | } | ||
166 | |||
167 | static int intel_hdmi_get_modes(struct drm_connector *connector) | ||
168 | { | ||
169 | struct intel_output *intel_output = to_intel_output(connector); | ||
170 | |||
171 | /* We should parse the EDID data and find out if it's an HDMI sink so | ||
172 | * we can send audio to it. | ||
173 | */ | ||
174 | |||
175 | return intel_ddc_get_modes(intel_output); | ||
176 | } | ||
177 | |||
178 | static void intel_hdmi_destroy(struct drm_connector *connector) | ||
179 | { | ||
180 | struct intel_output *intel_output = to_intel_output(connector); | ||
181 | |||
182 | if (intel_output->i2c_bus) | ||
183 | intel_i2c_destroy(intel_output->i2c_bus); | ||
184 | drm_sysfs_connector_remove(connector); | ||
185 | drm_connector_cleanup(connector); | ||
186 | kfree(intel_output); | ||
187 | } | ||
188 | |||
189 | static const struct drm_encoder_helper_funcs intel_hdmi_helper_funcs = { | ||
190 | .dpms = intel_hdmi_dpms, | ||
191 | .mode_fixup = intel_hdmi_mode_fixup, | ||
192 | .prepare = intel_encoder_prepare, | ||
193 | .mode_set = intel_hdmi_mode_set, | ||
194 | .commit = intel_encoder_commit, | ||
195 | }; | ||
196 | |||
197 | static const struct drm_connector_funcs intel_hdmi_connector_funcs = { | ||
198 | .save = intel_hdmi_save, | ||
199 | .restore = intel_hdmi_restore, | ||
200 | .detect = intel_hdmi_detect, | ||
201 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
202 | .destroy = intel_hdmi_destroy, | ||
203 | }; | ||
204 | |||
205 | static const struct drm_connector_helper_funcs intel_hdmi_connector_helper_funcs = { | ||
206 | .get_modes = intel_hdmi_get_modes, | ||
207 | .mode_valid = intel_hdmi_mode_valid, | ||
208 | .best_encoder = intel_best_encoder, | ||
209 | }; | ||
210 | |||
211 | static void intel_hdmi_enc_destroy(struct drm_encoder *encoder) | ||
212 | { | ||
213 | drm_encoder_cleanup(encoder); | ||
214 | } | ||
215 | |||
216 | static const struct drm_encoder_funcs intel_hdmi_enc_funcs = { | ||
217 | .destroy = intel_hdmi_enc_destroy, | ||
218 | }; | ||
219 | |||
220 | |||
221 | void intel_hdmi_init(struct drm_device *dev, int sdvox_reg) | ||
222 | { | ||
223 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
224 | struct drm_connector *connector; | ||
225 | struct intel_output *intel_output; | ||
226 | struct intel_hdmi_priv *hdmi_priv; | ||
227 | |||
228 | intel_output = kcalloc(sizeof(struct intel_output) + | ||
229 | sizeof(struct intel_hdmi_priv), 1, GFP_KERNEL); | ||
230 | if (!intel_output) | ||
231 | return; | ||
232 | hdmi_priv = (struct intel_hdmi_priv *)(intel_output + 1); | ||
233 | |||
234 | connector = &intel_output->base; | ||
235 | drm_connector_init(dev, connector, &intel_hdmi_connector_funcs, | ||
236 | DRM_MODE_CONNECTOR_DVID); | ||
237 | drm_connector_helper_add(connector, &intel_hdmi_connector_helper_funcs); | ||
238 | |||
239 | intel_output->type = INTEL_OUTPUT_HDMI; | ||
240 | |||
241 | connector->interlace_allowed = 0; | ||
242 | connector->doublescan_allowed = 0; | ||
243 | |||
244 | /* Set up the DDC bus. */ | ||
245 | if (sdvox_reg == SDVOB) | ||
246 | intel_output->ddc_bus = intel_i2c_create(dev, GPIOE, "HDMIB"); | ||
247 | else | ||
248 | intel_output->ddc_bus = intel_i2c_create(dev, GPIOD, "HDMIC"); | ||
249 | |||
250 | if (!intel_output->ddc_bus) | ||
251 | goto err_connector; | ||
252 | |||
253 | hdmi_priv->sdvox_reg = sdvox_reg; | ||
254 | intel_output->dev_priv = hdmi_priv; | ||
255 | |||
256 | drm_encoder_init(dev, &intel_output->enc, &intel_hdmi_enc_funcs, | ||
257 | DRM_MODE_ENCODER_TMDS); | ||
258 | drm_encoder_helper_add(&intel_output->enc, &intel_hdmi_helper_funcs); | ||
259 | |||
260 | drm_mode_connector_attach_encoder(&intel_output->base, | ||
261 | &intel_output->enc); | ||
262 | drm_sysfs_connector_add(connector); | ||
263 | |||
264 | /* For G4X desktop chip, PEG_BAND_GAP_DATA 3:0 must first be written | ||
265 | * 0xd. Failure to do so will result in spurious interrupts being | ||
266 | * generated on the port when a cable is not attached. | ||
267 | */ | ||
268 | if (IS_G4X(dev) && !IS_GM45(dev)) { | ||
269 | u32 temp = I915_READ(PEG_BAND_GAP_DATA); | ||
270 | I915_WRITE(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd); | ||
271 | } | ||
272 | |||
273 | return; | ||
274 | |||
275 | err_connector: | ||
276 | drm_connector_cleanup(connector); | ||
277 | kfree(intel_output); | ||
278 | |||
279 | return; | ||
280 | } | ||