1 | /* |
2 | * Copyright © 2006-2011 Intel Corporation |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice (including the next |
12 | * paragraph) shall be included in all copies or substantial portions of the |
13 | * Software. |
14 | * |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
21 | * DEALINGS IN THE SOFTWARE. |
22 | * |
23 | * Authors: |
24 | * jim liu <jim.liu@intel.com> |
25 | */ |
26 | |
27 | #include <linux/pm_runtime.h> |
28 | |
29 | #include <drm/drm.h> |
30 | #include <drm/drm_crtc.h> |
31 | #include <drm/drm_crtc_helper.h> |
32 | #include <drm/drm_edid.h> |
33 | #include <drm/drm_modeset_helper_vtables.h> |
34 | #include <drm/drm_simple_kms_helper.h> |
35 | |
36 | #include "cdv_device.h" |
37 | #include "psb_drv.h" |
38 | #include "psb_intel_drv.h" |
39 | #include "psb_intel_reg.h" |
40 | |
41 | /* hdmi control bits */ |
42 | #define HDMI_NULL_PACKETS_DURING_VSYNC (1 << 9) |
43 | #define HDMI_BORDER_ENABLE (1 << 7) |
44 | #define HDMI_AUDIO_ENABLE (1 << 6) |
45 | #define HDMI_VSYNC_ACTIVE_HIGH (1 << 4) |
46 | #define HDMI_HSYNC_ACTIVE_HIGH (1 << 3) |
47 | /* hdmi-b control bits */ |
48 | #define HDMIB_PIPE_B_SELECT (1 << 30) |
49 | |
50 | |
51 | struct mid_intel_hdmi_priv { |
52 | u32 hdmi_reg; |
53 | u32 save_HDMIB; |
54 | bool has_hdmi_sink; |
55 | bool has_hdmi_audio; |
56 | /* Should set this when detect hotplug */ |
57 | bool hdmi_device_connected; |
58 | struct drm_device *dev; |
59 | }; |
60 | |
61 | static void cdv_hdmi_mode_set(struct drm_encoder *encoder, |
62 | struct drm_display_mode *mode, |
63 | struct drm_display_mode *adjusted_mode) |
64 | { |
65 | struct drm_device *dev = encoder->dev; |
66 | struct gma_encoder *gma_encoder = to_gma_encoder(encoder); |
67 | struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; |
68 | u32 hdmib; |
69 | struct drm_crtc *crtc = encoder->crtc; |
70 | struct gma_crtc *gma_crtc = to_gma_crtc(crtc); |
71 | |
72 | hdmib = (2 << 10); |
73 | |
74 | if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) |
75 | hdmib |= HDMI_VSYNC_ACTIVE_HIGH; |
76 | if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) |
77 | hdmib |= HDMI_HSYNC_ACTIVE_HIGH; |
78 | |
79 | if (gma_crtc->pipe == 1) |
80 | hdmib |= HDMIB_PIPE_B_SELECT; |
81 | |
82 | if (hdmi_priv->has_hdmi_audio) { |
83 | hdmib |= HDMI_AUDIO_ENABLE; |
84 | hdmib |= HDMI_NULL_PACKETS_DURING_VSYNC; |
85 | } |
86 | |
87 | REG_WRITE(hdmi_priv->hdmi_reg, hdmib); |
88 | REG_READ(hdmi_priv->hdmi_reg); |
89 | } |
90 | |
91 | static void cdv_hdmi_dpms(struct drm_encoder *encoder, int mode) |
92 | { |
93 | struct drm_device *dev = encoder->dev; |
94 | struct gma_encoder *gma_encoder = to_gma_encoder(encoder); |
95 | struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; |
96 | u32 hdmib; |
97 | |
98 | hdmib = REG_READ(hdmi_priv->hdmi_reg); |
99 | |
100 | if (mode != DRM_MODE_DPMS_ON) |
101 | REG_WRITE(hdmi_priv->hdmi_reg, hdmib & ~HDMIB_PORT_EN); |
102 | else |
103 | REG_WRITE(hdmi_priv->hdmi_reg, hdmib | HDMIB_PORT_EN); |
104 | REG_READ(hdmi_priv->hdmi_reg); |
105 | } |
106 | |
107 | static void cdv_hdmi_save(struct drm_connector *connector) |
108 | { |
109 | struct drm_device *dev = connector->dev; |
110 | struct gma_encoder *gma_encoder = gma_attached_encoder(connector); |
111 | struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; |
112 | |
113 | hdmi_priv->save_HDMIB = REG_READ(hdmi_priv->hdmi_reg); |
114 | } |
115 | |
116 | static void cdv_hdmi_restore(struct drm_connector *connector) |
117 | { |
118 | struct drm_device *dev = connector->dev; |
119 | struct gma_encoder *gma_encoder = gma_attached_encoder(connector); |
120 | struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; |
121 | |
122 | REG_WRITE(hdmi_priv->hdmi_reg, hdmi_priv->save_HDMIB); |
123 | REG_READ(hdmi_priv->hdmi_reg); |
124 | } |
125 | |
126 | static enum drm_connector_status cdv_hdmi_detect( |
127 | struct drm_connector *connector, bool force) |
128 | { |
129 | struct gma_encoder *gma_encoder = gma_attached_encoder(connector); |
130 | struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; |
131 | struct edid *edid = NULL; |
132 | enum drm_connector_status status = connector_status_disconnected; |
133 | |
134 | edid = drm_get_edid(connector, adapter: connector->ddc); |
135 | |
136 | hdmi_priv->has_hdmi_sink = false; |
137 | hdmi_priv->has_hdmi_audio = false; |
138 | if (edid) { |
139 | if (edid->input & DRM_EDID_INPUT_DIGITAL) { |
140 | status = connector_status_connected; |
141 | hdmi_priv->has_hdmi_sink = |
142 | drm_detect_hdmi_monitor(edid); |
143 | hdmi_priv->has_hdmi_audio = |
144 | drm_detect_monitor_audio(edid); |
145 | } |
146 | kfree(objp: edid); |
147 | } |
148 | return status; |
149 | } |
150 | |
151 | static int cdv_hdmi_set_property(struct drm_connector *connector, |
152 | struct drm_property *property, |
153 | uint64_t value) |
154 | { |
155 | struct drm_encoder *encoder = connector->encoder; |
156 | |
157 | if (!strcmp(property->name, "scaling mode" ) && encoder) { |
158 | struct gma_crtc *crtc = to_gma_crtc(encoder->crtc); |
159 | bool centre; |
160 | uint64_t curValue; |
161 | |
162 | if (!crtc) |
163 | return -1; |
164 | |
165 | switch (value) { |
166 | case DRM_MODE_SCALE_FULLSCREEN: |
167 | break; |
168 | case DRM_MODE_SCALE_NO_SCALE: |
169 | break; |
170 | case DRM_MODE_SCALE_ASPECT: |
171 | break; |
172 | default: |
173 | return -1; |
174 | } |
175 | |
176 | if (drm_object_property_get_value(obj: &connector->base, |
177 | property, value: &curValue)) |
178 | return -1; |
179 | |
180 | if (curValue == value) |
181 | return 0; |
182 | |
183 | if (drm_object_property_set_value(obj: &connector->base, |
184 | property, val: value)) |
185 | return -1; |
186 | |
187 | centre = (curValue == DRM_MODE_SCALE_NO_SCALE) || |
188 | (value == DRM_MODE_SCALE_NO_SCALE); |
189 | |
190 | if (crtc->saved_mode.hdisplay != 0 && |
191 | crtc->saved_mode.vdisplay != 0) { |
192 | if (centre) { |
193 | if (!drm_crtc_helper_set_mode(crtc: encoder->crtc, mode: &crtc->saved_mode, |
194 | x: encoder->crtc->x, y: encoder->crtc->y, old_fb: encoder->crtc->primary->fb)) |
195 | return -1; |
196 | } else { |
197 | const struct drm_encoder_helper_funcs *helpers |
198 | = encoder->helper_private; |
199 | helpers->mode_set(encoder, &crtc->saved_mode, |
200 | &crtc->saved_adjusted_mode); |
201 | } |
202 | } |
203 | } |
204 | return 0; |
205 | } |
206 | |
207 | /* |
208 | * Return the list of HDMI DDC modes if available. |
209 | */ |
210 | static int cdv_hdmi_get_modes(struct drm_connector *connector) |
211 | { |
212 | struct edid *edid = NULL; |
213 | int ret = 0; |
214 | |
215 | edid = drm_get_edid(connector, adapter: connector->ddc); |
216 | if (edid) { |
217 | drm_connector_update_edid_property(connector, edid); |
218 | ret = drm_add_edid_modes(connector, edid); |
219 | kfree(objp: edid); |
220 | } |
221 | return ret; |
222 | } |
223 | |
224 | static enum drm_mode_status cdv_hdmi_mode_valid(struct drm_connector *connector, |
225 | struct drm_display_mode *mode) |
226 | { |
227 | if (mode->clock > 165000) |
228 | return MODE_CLOCK_HIGH; |
229 | if (mode->clock < 20000) |
230 | return MODE_CLOCK_HIGH; |
231 | |
232 | /* just in case */ |
233 | if (mode->flags & DRM_MODE_FLAG_DBLSCAN) |
234 | return MODE_NO_DBLESCAN; |
235 | |
236 | /* just in case */ |
237 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
238 | return MODE_NO_INTERLACE; |
239 | |
240 | return MODE_OK; |
241 | } |
242 | |
243 | static void cdv_hdmi_destroy(struct drm_connector *connector) |
244 | { |
245 | struct gma_connector *gma_connector = to_gma_connector(connector); |
246 | struct gma_i2c_chan *ddc_bus = to_gma_i2c_chan(connector->ddc); |
247 | |
248 | gma_i2c_destroy(chan: ddc_bus); |
249 | drm_connector_cleanup(connector); |
250 | kfree(objp: gma_connector); |
251 | } |
252 | |
253 | static const struct drm_encoder_helper_funcs cdv_hdmi_helper_funcs = { |
254 | .dpms = cdv_hdmi_dpms, |
255 | .prepare = gma_encoder_prepare, |
256 | .mode_set = cdv_hdmi_mode_set, |
257 | .commit = gma_encoder_commit, |
258 | }; |
259 | |
260 | static const struct drm_connector_helper_funcs |
261 | cdv_hdmi_connector_helper_funcs = { |
262 | .get_modes = cdv_hdmi_get_modes, |
263 | .mode_valid = cdv_hdmi_mode_valid, |
264 | .best_encoder = gma_best_encoder, |
265 | }; |
266 | |
267 | static const struct drm_connector_funcs cdv_hdmi_connector_funcs = { |
268 | .dpms = drm_helper_connector_dpms, |
269 | .detect = cdv_hdmi_detect, |
270 | .fill_modes = drm_helper_probe_single_connector_modes, |
271 | .set_property = cdv_hdmi_set_property, |
272 | .destroy = cdv_hdmi_destroy, |
273 | }; |
274 | |
275 | void cdv_hdmi_init(struct drm_device *dev, |
276 | struct psb_intel_mode_device *mode_dev, int reg) |
277 | { |
278 | struct gma_encoder *gma_encoder; |
279 | struct gma_connector *gma_connector; |
280 | struct drm_connector *connector; |
281 | struct mid_intel_hdmi_priv *hdmi_priv; |
282 | struct gma_i2c_chan *ddc_bus; |
283 | int ddc_reg; |
284 | int ret; |
285 | |
286 | gma_encoder = kzalloc(size: sizeof(struct gma_encoder), GFP_KERNEL); |
287 | if (!gma_encoder) |
288 | return; |
289 | |
290 | gma_connector = kzalloc(size: sizeof(struct gma_connector), GFP_KERNEL); |
291 | if (!gma_connector) |
292 | goto err_free_encoder; |
293 | |
294 | hdmi_priv = kzalloc(size: sizeof(struct mid_intel_hdmi_priv), GFP_KERNEL); |
295 | if (!hdmi_priv) |
296 | goto err_free_connector; |
297 | |
298 | connector = &gma_connector->base; |
299 | connector->polled = DRM_CONNECTOR_POLL_HPD; |
300 | gma_connector->save = cdv_hdmi_save; |
301 | gma_connector->restore = cdv_hdmi_restore; |
302 | |
303 | switch (reg) { |
304 | case SDVOB: |
305 | ddc_reg = GPIOE; |
306 | gma_encoder->ddi_select = DDI0_SELECT; |
307 | break; |
308 | case SDVOC: |
309 | ddc_reg = GPIOD; |
310 | gma_encoder->ddi_select = DDI1_SELECT; |
311 | break; |
312 | default: |
313 | DRM_ERROR("unknown reg 0x%x for HDMI\n" , reg); |
314 | goto err_free_hdmi_priv; |
315 | } |
316 | |
317 | ddc_bus = gma_i2c_create(dev, reg: ddc_reg, |
318 | name: (reg == SDVOB) ? "HDMIB" : "HDMIC" ); |
319 | if (!ddc_bus) { |
320 | dev_err(dev->dev, "No ddc adapter available!\n" ); |
321 | goto err_free_hdmi_priv; |
322 | } |
323 | |
324 | ret = drm_connector_init_with_ddc(dev, connector, |
325 | funcs: &cdv_hdmi_connector_funcs, |
326 | DRM_MODE_CONNECTOR_DVID, |
327 | ddc: &ddc_bus->base); |
328 | if (ret) |
329 | goto err_ddc_destroy; |
330 | |
331 | ret = drm_simple_encoder_init(dev, encoder: &gma_encoder->base, |
332 | DRM_MODE_ENCODER_TMDS); |
333 | if (ret) |
334 | goto err_connector_cleanup; |
335 | |
336 | gma_connector_attach_encoder(connector: gma_connector, encoder: gma_encoder); |
337 | gma_encoder->type = INTEL_OUTPUT_HDMI; |
338 | hdmi_priv->hdmi_reg = reg; |
339 | hdmi_priv->has_hdmi_sink = false; |
340 | gma_encoder->dev_priv = hdmi_priv; |
341 | |
342 | drm_encoder_helper_add(encoder: &gma_encoder->base, funcs: &cdv_hdmi_helper_funcs); |
343 | drm_connector_helper_add(connector, |
344 | funcs: &cdv_hdmi_connector_helper_funcs); |
345 | connector->display_info.subpixel_order = SubPixelHorizontalRGB; |
346 | connector->interlace_allowed = false; |
347 | connector->doublescan_allowed = false; |
348 | |
349 | drm_object_attach_property(obj: &connector->base, |
350 | property: dev->mode_config.scaling_mode_property, |
351 | DRM_MODE_SCALE_FULLSCREEN); |
352 | |
353 | hdmi_priv->dev = dev; |
354 | return; |
355 | |
356 | err_connector_cleanup: |
357 | drm_connector_cleanup(connector); |
358 | err_ddc_destroy: |
359 | gma_i2c_destroy(chan: ddc_bus); |
360 | err_free_hdmi_priv: |
361 | kfree(objp: hdmi_priv); |
362 | err_free_connector: |
363 | kfree(objp: gma_connector); |
364 | err_free_encoder: |
365 | kfree(objp: gma_encoder); |
366 | } |
367 | |