Commit | Line | Data |
---|---|---|
d8408326 SWK |
1 | /* |
2 | * Copyright (C) 2011 Samsung Electronics Co.Ltd | |
3 | * Authors: | |
4 | * Inki Dae <inki.dae@samsung.com> | |
5 | * Seung-Woo Kim <sw0312.kim@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License as published by the | |
9 | * Free Software Foundation; either version 2 of the License, or (at your | |
10 | * option) any later version. | |
11 | * | |
12 | */ | |
13 | ||
760285e7 | 14 | #include <drm/drmP.h> |
d8408326 SWK |
15 | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/wait.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/pm_runtime.h> | |
21 | ||
22 | #include <drm/exynos_drm.h> | |
23 | ||
24 | #include "exynos_drm_drv.h" | |
25 | #include "exynos_drm_hdmi.h" | |
26 | ||
27 | #define to_context(dev) platform_get_drvdata(to_platform_device(dev)) | |
28 | #define to_subdrv(dev) to_context(dev) | |
29 | #define get_ctx_from_subdrv(subdrv) container_of(subdrv,\ | |
30 | struct drm_hdmi_context, subdrv); | |
31 | ||
ae9dace2 RS |
32 | /* platform device pointer for common drm hdmi device. */ |
33 | static struct platform_device *exynos_drm_hdmi_pdev; | |
34 | ||
768c3059 RS |
35 | /* Common hdmi subdrv needs to access the hdmi and mixer though context. |
36 | * These should be initialied by the repective drivers */ | |
37 | static struct exynos_drm_hdmi_context *hdmi_ctx; | |
38 | static struct exynos_drm_hdmi_context *mixer_ctx; | |
39 | ||
d8408326 | 40 | /* these callback points shoud be set by specific drivers. */ |
578b6065 JS |
41 | static struct exynos_hdmi_ops *hdmi_ops; |
42 | static struct exynos_mixer_ops *mixer_ops; | |
d8408326 SWK |
43 | |
44 | struct drm_hdmi_context { | |
45 | struct exynos_drm_subdrv subdrv; | |
46 | struct exynos_drm_hdmi_context *hdmi_ctx; | |
47 | struct exynos_drm_hdmi_context *mixer_ctx; | |
cf8fc4f1 JS |
48 | |
49 | bool enabled[MIXER_WIN_NR]; | |
d8408326 SWK |
50 | }; |
51 | ||
ae9dace2 RS |
52 | int exynos_platform_device_hdmi_register(void) |
53 | { | |
0f6f9592 SWK |
54 | struct platform_device *pdev; |
55 | ||
ae9dace2 RS |
56 | if (exynos_drm_hdmi_pdev) |
57 | return -EEXIST; | |
58 | ||
0f6f9592 | 59 | pdev = platform_device_register_simple( |
ae9dace2 | 60 | "exynos-drm-hdmi", -1, NULL, 0); |
0f6f9592 SWK |
61 | if (IS_ERR(pdev)) |
62 | return PTR_ERR(pdev); | |
63 | ||
64 | exynos_drm_hdmi_pdev = pdev; | |
ae9dace2 RS |
65 | |
66 | return 0; | |
67 | } | |
68 | ||
69 | void exynos_platform_device_hdmi_unregister(void) | |
70 | { | |
0f6f9592 | 71 | if (exynos_drm_hdmi_pdev) { |
ae9dace2 | 72 | platform_device_unregister(exynos_drm_hdmi_pdev); |
0f6f9592 SWK |
73 | exynos_drm_hdmi_pdev = NULL; |
74 | } | |
ae9dace2 RS |
75 | } |
76 | ||
768c3059 RS |
77 | void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx) |
78 | { | |
79 | if (ctx) | |
80 | hdmi_ctx = ctx; | |
81 | } | |
82 | ||
83 | void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx) | |
84 | { | |
85 | if (ctx) | |
86 | mixer_ctx = ctx; | |
87 | } | |
88 | ||
578b6065 | 89 | void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops) |
d8408326 SWK |
90 | { |
91 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
92 | ||
578b6065 JS |
93 | if (ops) |
94 | hdmi_ops = ops; | |
d8408326 | 95 | } |
d8408326 | 96 | |
578b6065 | 97 | void exynos_mixer_ops_register(struct exynos_mixer_ops *ops) |
d8408326 SWK |
98 | { |
99 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
100 | ||
578b6065 JS |
101 | if (ops) |
102 | mixer_ops = ops; | |
d8408326 | 103 | } |
d8408326 SWK |
104 | |
105 | static bool drm_hdmi_is_connected(struct device *dev) | |
106 | { | |
107 | struct drm_hdmi_context *ctx = to_context(dev); | |
108 | ||
109 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
110 | ||
578b6065 JS |
111 | if (hdmi_ops && hdmi_ops->is_connected) |
112 | return hdmi_ops->is_connected(ctx->hdmi_ctx->ctx); | |
d8408326 SWK |
113 | |
114 | return false; | |
115 | } | |
116 | ||
428982e7 | 117 | static struct edid *drm_hdmi_get_edid(struct device *dev, |
9c08e4ba | 118 | struct drm_connector *connector) |
d8408326 SWK |
119 | { |
120 | struct drm_hdmi_context *ctx = to_context(dev); | |
121 | ||
122 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
123 | ||
578b6065 | 124 | if (hdmi_ops && hdmi_ops->get_edid) |
9c08e4ba | 125 | return hdmi_ops->get_edid(ctx->hdmi_ctx->ctx, connector); |
d8408326 | 126 | |
9c08e4ba | 127 | return NULL; |
d8408326 SWK |
128 | } |
129 | ||
130 | static int drm_hdmi_check_timing(struct device *dev, void *timing) | |
131 | { | |
132 | struct drm_hdmi_context *ctx = to_context(dev); | |
438c0f85 | 133 | int ret = 0; |
d8408326 SWK |
134 | |
135 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
136 | ||
438c0f85 RS |
137 | /* |
138 | * Both, mixer and hdmi should be able to handle the requested mode. | |
139 | * If any of the two fails, return mode as BAD. | |
140 | */ | |
141 | ||
142 | if (mixer_ops && mixer_ops->check_timing) | |
143 | ret = mixer_ops->check_timing(ctx->mixer_ctx->ctx, timing); | |
144 | ||
145 | if (ret) | |
146 | return ret; | |
147 | ||
578b6065 JS |
148 | if (hdmi_ops && hdmi_ops->check_timing) |
149 | return hdmi_ops->check_timing(ctx->hdmi_ctx->ctx, timing); | |
d8408326 SWK |
150 | |
151 | return 0; | |
152 | } | |
153 | ||
154 | static int drm_hdmi_power_on(struct device *dev, int mode) | |
155 | { | |
156 | struct drm_hdmi_context *ctx = to_context(dev); | |
157 | ||
158 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
159 | ||
578b6065 JS |
160 | if (hdmi_ops && hdmi_ops->power_on) |
161 | return hdmi_ops->power_on(ctx->hdmi_ctx->ctx, mode); | |
d8408326 SWK |
162 | |
163 | return 0; | |
164 | } | |
165 | ||
166 | static struct exynos_drm_display_ops drm_hdmi_display_ops = { | |
167 | .type = EXYNOS_DISPLAY_TYPE_HDMI, | |
168 | .is_connected = drm_hdmi_is_connected, | |
169 | .get_edid = drm_hdmi_get_edid, | |
170 | .check_timing = drm_hdmi_check_timing, | |
171 | .power_on = drm_hdmi_power_on, | |
172 | }; | |
173 | ||
174 | static int drm_hdmi_enable_vblank(struct device *subdrv_dev) | |
175 | { | |
176 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
177 | struct exynos_drm_subdrv *subdrv = &ctx->subdrv; | |
677e84c1 | 178 | struct exynos_drm_manager *manager = subdrv->manager; |
d8408326 SWK |
179 | |
180 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
181 | ||
578b6065 JS |
182 | if (mixer_ops && mixer_ops->enable_vblank) |
183 | return mixer_ops->enable_vblank(ctx->mixer_ctx->ctx, | |
184 | manager->pipe); | |
d8408326 SWK |
185 | |
186 | return 0; | |
187 | } | |
188 | ||
189 | static void drm_hdmi_disable_vblank(struct device *subdrv_dev) | |
190 | { | |
191 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
192 | ||
193 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
194 | ||
578b6065 JS |
195 | if (mixer_ops && mixer_ops->disable_vblank) |
196 | return mixer_ops->disable_vblank(ctx->mixer_ctx->ctx); | |
d8408326 SWK |
197 | } |
198 | ||
8137a2e2 P |
199 | static void drm_hdmi_wait_for_vblank(struct device *subdrv_dev) |
200 | { | |
201 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
202 | ||
203 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
204 | ||
205 | if (mixer_ops && mixer_ops->wait_for_vblank) | |
206 | mixer_ops->wait_for_vblank(ctx->mixer_ctx->ctx); | |
207 | } | |
208 | ||
1de425b0 ID |
209 | static void drm_hdmi_mode_fixup(struct device *subdrv_dev, |
210 | struct drm_connector *connector, | |
e811f5ae | 211 | const struct drm_display_mode *mode, |
1de425b0 ID |
212 | struct drm_display_mode *adjusted_mode) |
213 | { | |
7ddcc736 RS |
214 | struct drm_display_mode *m; |
215 | int mode_ok; | |
1de425b0 ID |
216 | |
217 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
218 | ||
7ddcc736 RS |
219 | drm_mode_set_crtcinfo(adjusted_mode, 0); |
220 | ||
221 | mode_ok = drm_hdmi_check_timing(subdrv_dev, adjusted_mode); | |
222 | ||
223 | /* just return if user desired mode exists. */ | |
224 | if (mode_ok == 0) | |
225 | return; | |
226 | ||
227 | /* | |
228 | * otherwise, find the most suitable mode among modes and change it | |
229 | * to adjusted_mode. | |
230 | */ | |
231 | list_for_each_entry(m, &connector->modes, head) { | |
232 | mode_ok = drm_hdmi_check_timing(subdrv_dev, m); | |
233 | ||
234 | if (mode_ok == 0) { | |
235 | struct drm_mode_object base; | |
236 | struct list_head head; | |
237 | ||
238 | DRM_INFO("desired mode doesn't exist so\n"); | |
239 | DRM_INFO("use the most suitable mode among modes.\n"); | |
240 | ||
241 | DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n", | |
242 | m->hdisplay, m->vdisplay, m->vrefresh); | |
243 | ||
244 | /* preserve display mode header while copying. */ | |
245 | head = adjusted_mode->head; | |
246 | base = adjusted_mode->base; | |
247 | memcpy(adjusted_mode, m, sizeof(*m)); | |
248 | adjusted_mode->head = head; | |
249 | adjusted_mode->base = base; | |
250 | break; | |
251 | } | |
252 | } | |
1de425b0 ID |
253 | } |
254 | ||
d8408326 SWK |
255 | static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode) |
256 | { | |
257 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
258 | ||
259 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
260 | ||
578b6065 JS |
261 | if (hdmi_ops && hdmi_ops->mode_set) |
262 | hdmi_ops->mode_set(ctx->hdmi_ctx->ctx, mode); | |
d8408326 SWK |
263 | } |
264 | ||
1de425b0 ID |
265 | static void drm_hdmi_get_max_resol(struct device *subdrv_dev, |
266 | unsigned int *width, unsigned int *height) | |
267 | { | |
268 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
269 | ||
270 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
271 | ||
578b6065 JS |
272 | if (hdmi_ops && hdmi_ops->get_max_resol) |
273 | hdmi_ops->get_max_resol(ctx->hdmi_ctx->ctx, width, height); | |
1de425b0 ID |
274 | } |
275 | ||
d8408326 SWK |
276 | static void drm_hdmi_commit(struct device *subdrv_dev) |
277 | { | |
278 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
279 | ||
280 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
281 | ||
578b6065 JS |
282 | if (hdmi_ops && hdmi_ops->commit) |
283 | hdmi_ops->commit(ctx->hdmi_ctx->ctx); | |
d8408326 SWK |
284 | } |
285 | ||
286 | static void drm_hdmi_dpms(struct device *subdrv_dev, int mode) | |
287 | { | |
288 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
289 | ||
290 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
291 | ||
cf8fc4f1 JS |
292 | if (mixer_ops && mixer_ops->dpms) |
293 | mixer_ops->dpms(ctx->mixer_ctx->ctx, mode); | |
294 | ||
295 | if (hdmi_ops && hdmi_ops->dpms) | |
296 | hdmi_ops->dpms(ctx->hdmi_ctx->ctx, mode); | |
297 | } | |
298 | ||
299 | static void drm_hdmi_apply(struct device *subdrv_dev) | |
300 | { | |
301 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
302 | int i; | |
303 | ||
304 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
305 | ||
306 | for (i = 0; i < MIXER_WIN_NR; i++) { | |
307 | if (!ctx->enabled[i]) | |
308 | continue; | |
309 | if (mixer_ops && mixer_ops->win_commit) | |
310 | mixer_ops->win_commit(ctx->mixer_ctx->ctx, i); | |
d8408326 | 311 | } |
cf8fc4f1 JS |
312 | |
313 | if (hdmi_ops && hdmi_ops->commit) | |
314 | hdmi_ops->commit(ctx->hdmi_ctx->ctx); | |
d8408326 SWK |
315 | } |
316 | ||
317 | static struct exynos_drm_manager_ops drm_hdmi_manager_ops = { | |
318 | .dpms = drm_hdmi_dpms, | |
cf8fc4f1 | 319 | .apply = drm_hdmi_apply, |
d8408326 SWK |
320 | .enable_vblank = drm_hdmi_enable_vblank, |
321 | .disable_vblank = drm_hdmi_disable_vblank, | |
8137a2e2 | 322 | .wait_for_vblank = drm_hdmi_wait_for_vblank, |
1de425b0 | 323 | .mode_fixup = drm_hdmi_mode_fixup, |
d8408326 | 324 | .mode_set = drm_hdmi_mode_set, |
1de425b0 | 325 | .get_max_resol = drm_hdmi_get_max_resol, |
d8408326 SWK |
326 | .commit = drm_hdmi_commit, |
327 | }; | |
328 | ||
329 | static void drm_mixer_mode_set(struct device *subdrv_dev, | |
330 | struct exynos_drm_overlay *overlay) | |
331 | { | |
332 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
333 | ||
334 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
335 | ||
578b6065 JS |
336 | if (mixer_ops && mixer_ops->win_mode_set) |
337 | mixer_ops->win_mode_set(ctx->mixer_ctx->ctx, overlay); | |
d8408326 SWK |
338 | } |
339 | ||
340 | static void drm_mixer_commit(struct device *subdrv_dev, int zpos) | |
341 | { | |
342 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
cf8fc4f1 | 343 | int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos; |
d8408326 SWK |
344 | |
345 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
346 | ||
cf8fc4f1 JS |
347 | if (win < 0 || win > MIXER_WIN_NR) { |
348 | DRM_ERROR("mixer window[%d] is wrong\n", win); | |
349 | return; | |
350 | } | |
351 | ||
578b6065 | 352 | if (mixer_ops && mixer_ops->win_commit) |
cf8fc4f1 JS |
353 | mixer_ops->win_commit(ctx->mixer_ctx->ctx, win); |
354 | ||
355 | ctx->enabled[win] = true; | |
d8408326 SWK |
356 | } |
357 | ||
358 | static void drm_mixer_disable(struct device *subdrv_dev, int zpos) | |
359 | { | |
360 | struct drm_hdmi_context *ctx = to_context(subdrv_dev); | |
cf8fc4f1 | 361 | int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos; |
d8408326 SWK |
362 | |
363 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
364 | ||
cf8fc4f1 JS |
365 | if (win < 0 || win > MIXER_WIN_NR) { |
366 | DRM_ERROR("mixer window[%d] is wrong\n", win); | |
367 | return; | |
368 | } | |
369 | ||
578b6065 | 370 | if (mixer_ops && mixer_ops->win_disable) |
cf8fc4f1 JS |
371 | mixer_ops->win_disable(ctx->mixer_ctx->ctx, win); |
372 | ||
373 | ctx->enabled[win] = false; | |
d8408326 SWK |
374 | } |
375 | ||
376 | static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = { | |
377 | .mode_set = drm_mixer_mode_set, | |
378 | .commit = drm_mixer_commit, | |
379 | .disable = drm_mixer_disable, | |
380 | }; | |
381 | ||
677e84c1 JS |
382 | static struct exynos_drm_manager hdmi_manager = { |
383 | .pipe = -1, | |
384 | .ops = &drm_hdmi_manager_ops, | |
385 | .overlay_ops = &drm_hdmi_overlay_ops, | |
386 | .display_ops = &drm_hdmi_display_ops, | |
387 | }; | |
d8408326 SWK |
388 | |
389 | static int hdmi_subdrv_probe(struct drm_device *drm_dev, | |
390 | struct device *dev) | |
391 | { | |
392 | struct exynos_drm_subdrv *subdrv = to_subdrv(dev); | |
393 | struct drm_hdmi_context *ctx; | |
d8408326 SWK |
394 | |
395 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
396 | ||
768c3059 RS |
397 | if (!hdmi_ctx) { |
398 | DRM_ERROR("hdmi context not initialized.\n"); | |
d8408326 SWK |
399 | return -EFAULT; |
400 | } | |
401 | ||
768c3059 RS |
402 | if (!mixer_ctx) { |
403 | DRM_ERROR("mixer context not initialized.\n"); | |
d8408326 SWK |
404 | return -EFAULT; |
405 | } | |
406 | ||
d8408326 SWK |
407 | ctx = get_ctx_from_subdrv(subdrv); |
408 | ||
768c3059 RS |
409 | if (!ctx) { |
410 | DRM_ERROR("no drm hdmi context.\n"); | |
132a5b91 | 411 | return -EFAULT; |
d8408326 SWK |
412 | } |
413 | ||
768c3059 RS |
414 | ctx->hdmi_ctx = hdmi_ctx; |
415 | ctx->mixer_ctx = mixer_ctx; | |
d8408326 | 416 | |
768c3059 | 417 | ctx->hdmi_ctx->drm_dev = drm_dev; |
d8408326 SWK |
418 | ctx->mixer_ctx->drm_dev = drm_dev; |
419 | ||
1055b39f ID |
420 | if (mixer_ops->iommu_on) |
421 | mixer_ops->iommu_on(ctx->mixer_ctx->ctx, true); | |
422 | ||
d8408326 | 423 | return 0; |
d8408326 SWK |
424 | } |
425 | ||
1055b39f ID |
426 | static void hdmi_subdrv_remove(struct drm_device *drm_dev, struct device *dev) |
427 | { | |
428 | struct drm_hdmi_context *ctx; | |
429 | struct exynos_drm_subdrv *subdrv = to_subdrv(dev); | |
430 | ||
431 | ctx = get_ctx_from_subdrv(subdrv); | |
432 | ||
433 | if (mixer_ops->iommu_on) | |
434 | mixer_ops->iommu_on(ctx->mixer_ctx->ctx, false); | |
435 | } | |
436 | ||
56550d94 | 437 | static int exynos_drm_hdmi_probe(struct platform_device *pdev) |
d8408326 SWK |
438 | { |
439 | struct device *dev = &pdev->dev; | |
440 | struct exynos_drm_subdrv *subdrv; | |
441 | struct drm_hdmi_context *ctx; | |
442 | ||
443 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
444 | ||
d873ab99 | 445 | ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); |
d8408326 SWK |
446 | if (!ctx) { |
447 | DRM_LOG_KMS("failed to alloc common hdmi context.\n"); | |
448 | return -ENOMEM; | |
449 | } | |
450 | ||
451 | subdrv = &ctx->subdrv; | |
452 | ||
677e84c1 JS |
453 | subdrv->dev = dev; |
454 | subdrv->manager = &hdmi_manager; | |
d8408326 | 455 | subdrv->probe = hdmi_subdrv_probe; |
1055b39f | 456 | subdrv->remove = hdmi_subdrv_remove; |
d8408326 SWK |
457 | |
458 | platform_set_drvdata(pdev, subdrv); | |
459 | ||
132a5b91 | 460 | exynos_drm_subdrv_register(subdrv); |
d8408326 SWK |
461 | |
462 | return 0; | |
463 | } | |
464 | ||
56550d94 | 465 | static int exynos_drm_hdmi_remove(struct platform_device *pdev) |
d8408326 SWK |
466 | { |
467 | struct drm_hdmi_context *ctx = platform_get_drvdata(pdev); | |
468 | ||
469 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
470 | ||
471 | exynos_drm_subdrv_unregister(&ctx->subdrv); | |
d8408326 SWK |
472 | |
473 | return 0; | |
474 | } | |
475 | ||
132a5b91 | 476 | struct platform_driver exynos_drm_common_hdmi_driver = { |
d8408326 | 477 | .probe = exynos_drm_hdmi_probe, |
56550d94 | 478 | .remove = exynos_drm_hdmi_remove, |
d8408326 SWK |
479 | .driver = { |
480 | .name = "exynos-drm-hdmi", | |
481 | .owner = THIS_MODULE, | |
d8408326 SWK |
482 | }, |
483 | }; |