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