]>
Commit | Line | Data |
---|---|---|
e692da4d SH |
1 | /* |
2 | * Freescale i.MX drm driver | |
3 | * | |
4 | * Copyright (C) 2011 Sascha Hauer, Pengutronix | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License | |
8 | * as published by the Free Software Foundation; either version 2 | |
9 | * of the License, or (at your option) any later version. | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | */ | |
17b5001b | 16 | #include <linux/component.h> |
e692da4d | 17 | #include <linux/device.h> |
5f2f9115 | 18 | #include <linux/dma-buf.h> |
e7d6231e | 19 | #include <linux/module.h> |
e692da4d SH |
20 | #include <linux/platform_device.h> |
21 | #include <drm/drmP.h> | |
5f2f9115 LY |
22 | #include <drm/drm_atomic.h> |
23 | #include <drm/drm_atomic_helper.h> | |
e692da4d SH |
24 | #include <drm/drm_fb_helper.h> |
25 | #include <drm/drm_crtc_helper.h> | |
e692da4d SH |
26 | #include <drm/drm_gem_cma_helper.h> |
27 | #include <drm/drm_fb_cma_helper.h> | |
3cb9ae4f | 28 | #include <drm/drm_plane_helper.h> |
6457b971 | 29 | #include <drm/drm_of.h> |
310944d1 | 30 | #include <video/imx-ipu-v3.h> |
e692da4d SH |
31 | |
32 | #include "imx-drm.h" | |
33 | ||
34 | #define MAX_CRTC 4 | |
35 | ||
655b43cc PZ |
36 | struct imx_drm_component { |
37 | struct device_node *of_node; | |
38 | struct list_head list; | |
39 | }; | |
40 | ||
e692da4d SH |
41 | struct imx_drm_device { |
42 | struct drm_device *drm; | |
d2ab8ad9 | 43 | unsigned int pipes; |
e692da4d | 44 | struct drm_fbdev_cma *fbhelper; |
5f2f9115 | 45 | struct drm_atomic_state *state; |
e692da4d SH |
46 | }; |
47 | ||
c1ff5a7a | 48 | #if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) |
8acba02f RK |
49 | static int legacyfb_depth = 16; |
50 | module_param(legacyfb_depth, int, 0444); | |
c1ff5a7a | 51 | #endif |
8acba02f | 52 | |
e692da4d SH |
53 | static void imx_drm_driver_lastclose(struct drm_device *drm) |
54 | { | |
55 | struct imx_drm_device *imxdrm = drm->dev_private; | |
56 | ||
3f3a7280 | 57 | drm_fbdev_cma_restore_mode(imxdrm->fbhelper); |
e692da4d SH |
58 | } |
59 | ||
e692da4d SH |
60 | static const struct file_operations imx_drm_driver_fops = { |
61 | .owner = THIS_MODULE, | |
62 | .open = drm_open, | |
63 | .release = drm_release, | |
64 | .unlocked_ioctl = drm_ioctl, | |
65 | .mmap = drm_gem_cma_mmap, | |
66 | .poll = drm_poll, | |
e692da4d SH |
67 | .read = drm_read, |
68 | .llseek = noop_llseek, | |
69 | }; | |
70 | ||
8a51a33b RK |
71 | void imx_drm_connector_destroy(struct drm_connector *connector) |
72 | { | |
34ea3d38 | 73 | drm_connector_unregister(connector); |
8a51a33b RK |
74 | drm_connector_cleanup(connector); |
75 | } | |
76 | EXPORT_SYMBOL_GPL(imx_drm_connector_destroy); | |
77 | ||
78 | void imx_drm_encoder_destroy(struct drm_encoder *encoder) | |
79 | { | |
80 | drm_encoder_cleanup(encoder); | |
81 | } | |
82 | EXPORT_SYMBOL_GPL(imx_drm_encoder_destroy); | |
83 | ||
3e68439b RK |
84 | static void imx_drm_output_poll_changed(struct drm_device *drm) |
85 | { | |
3e68439b RK |
86 | struct imx_drm_device *imxdrm = drm->dev_private; |
87 | ||
88 | drm_fbdev_cma_hotplug_event(imxdrm->fbhelper); | |
3e68439b RK |
89 | } |
90 | ||
1780999c LY |
91 | static int imx_drm_atomic_check(struct drm_device *dev, |
92 | struct drm_atomic_state *state) | |
93 | { | |
94 | int ret; | |
95 | ||
96 | ret = drm_atomic_helper_check_modeset(dev, state); | |
97 | if (ret) | |
98 | return ret; | |
99 | ||
100 | ret = drm_atomic_helper_check_planes(dev, state); | |
101 | if (ret) | |
102 | return ret; | |
103 | ||
104 | /* | |
105 | * Check modeset again in case crtc_state->mode_changed is | |
106 | * updated in plane's ->atomic_check callback. | |
107 | */ | |
108 | ret = drm_atomic_helper_check_modeset(dev, state); | |
109 | if (ret) | |
110 | return ret; | |
111 | ||
112 | return ret; | |
113 | } | |
114 | ||
7ae847dd | 115 | static const struct drm_mode_config_funcs imx_drm_mode_config_funcs = { |
1df8b530 | 116 | .fb_create = drm_fb_cma_create, |
3e68439b | 117 | .output_poll_changed = imx_drm_output_poll_changed, |
1780999c | 118 | .atomic_check = imx_drm_atomic_check, |
782ea2a4 | 119 | .atomic_commit = drm_atomic_helper_commit, |
5f2f9115 LY |
120 | }; |
121 | ||
122 | static void imx_drm_atomic_commit_tail(struct drm_atomic_state *state) | |
123 | { | |
124 | struct drm_device *dev = state->dev; | |
5f2f9115 LY |
125 | |
126 | drm_atomic_helper_commit_modeset_disables(dev, state); | |
127 | ||
2b58e98d | 128 | drm_atomic_helper_commit_planes(dev, state, |
5f4df0c7 LY |
129 | DRM_PLANE_COMMIT_ACTIVE_ONLY | |
130 | DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET); | |
5f2f9115 LY |
131 | |
132 | drm_atomic_helper_commit_modeset_enables(dev, state); | |
133 | ||
134 | drm_atomic_helper_commit_hw_done(state); | |
135 | ||
136 | drm_atomic_helper_wait_for_vblanks(dev, state); | |
137 | ||
138 | drm_atomic_helper_cleanup_planes(dev, state); | |
139 | } | |
140 | ||
a4b10cce | 141 | static const struct drm_mode_config_helper_funcs imx_drm_mode_config_helpers = { |
5f2f9115 | 142 | .atomic_commit_tail = imx_drm_atomic_commit_tail, |
1df8b530 RK |
143 | }; |
144 | ||
e692da4d | 145 | |
6457b971 RK |
146 | int imx_drm_encoder_parse_of(struct drm_device *drm, |
147 | struct drm_encoder *encoder, struct device_node *np) | |
9e2d410d | 148 | { |
6457b971 | 149 | uint32_t crtc_mask = drm_of_find_possible_crtcs(drm, np); |
9e2d410d | 150 | |
6457b971 RK |
151 | /* |
152 | * If we failed to find the CRTC(s) which this encoder is | |
153 | * supposed to be connected to, it's because the CRTC has | |
154 | * not been registered yet. Defer probing, and hope that | |
155 | * the required CRTC is added later. | |
156 | */ | |
157 | if (crtc_mask == 0) | |
158 | return -EPROBE_DEFER; | |
655b43cc | 159 | |
6457b971 | 160 | encoder->possible_crtcs = crtc_mask; |
63bc5164 | 161 | |
6457b971 RK |
162 | /* FIXME: this is the mask of outputs which can clone this output. */ |
163 | encoder->possible_clones = ~0; | |
9e2d410d RK |
164 | |
165 | return 0; | |
166 | } | |
6457b971 | 167 | EXPORT_SYMBOL_GPL(imx_drm_encoder_parse_of); |
9e2d410d | 168 | |
baa70943 | 169 | static const struct drm_ioctl_desc imx_drm_ioctls[] = { |
e692da4d SH |
170 | /* none so far */ |
171 | }; | |
172 | ||
173 | static struct drm_driver imx_drm_driver = { | |
8535c022 LY |
174 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | |
175 | DRIVER_ATOMIC, | |
e692da4d | 176 | .lastclose = imx_drm_driver_lastclose, |
4b193663 | 177 | .gem_free_object_unlocked = drm_gem_cma_free_object, |
e692da4d SH |
178 | .gem_vm_ops = &drm_gem_cma_vm_ops, |
179 | .dumb_create = drm_gem_cma_dumb_create, | |
180 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, | |
43387b37 | 181 | .dumb_destroy = drm_gem_dumb_destroy, |
e692da4d | 182 | |
bd3665c9 PZ |
183 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, |
184 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
185 | .gem_prime_import = drm_gem_prime_import, | |
186 | .gem_prime_export = drm_gem_prime_export, | |
187 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | |
188 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | |
189 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | |
190 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | |
191 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | |
e692da4d SH |
192 | .ioctls = imx_drm_ioctls, |
193 | .num_ioctls = ARRAY_SIZE(imx_drm_ioctls), | |
194 | .fops = &imx_drm_driver_fops, | |
195 | .name = "imx-drm", | |
196 | .desc = "i.MX DRM graphics", | |
197 | .date = "20120507", | |
198 | .major = 1, | |
199 | .minor = 0, | |
200 | .patchlevel = 0, | |
201 | }; | |
202 | ||
17b5001b RK |
203 | static int compare_of(struct device *dev, void *data) |
204 | { | |
655b43cc | 205 | struct device_node *np = data; |
17b5001b | 206 | |
310944d1 PZ |
207 | /* Special case for DI, dev->of_node may not be set yet */ |
208 | if (strcmp(dev->driver->name, "imx-ipuv3-crtc") == 0) { | |
209 | struct ipu_client_platformdata *pdata = dev->platform_data; | |
210 | ||
211 | return pdata->of_node == np; | |
212 | } | |
213 | ||
655b43cc PZ |
214 | /* Special case for LDB, one device for two channels */ |
215 | if (of_node_cmp(np->name, "lvds-channel") == 0) { | |
216 | np = of_get_parent(np); | |
217 | of_node_put(np); | |
17b5001b RK |
218 | } |
219 | ||
655b43cc PZ |
220 | return dev->of_node == np; |
221 | } | |
17b5001b | 222 | |
17b5001b RK |
223 | static int imx_drm_bind(struct device *dev) |
224 | { | |
54db5dec LS |
225 | struct drm_device *drm; |
226 | struct imx_drm_device *imxdrm; | |
227 | int ret; | |
228 | ||
229 | drm = drm_dev_alloc(&imx_drm_driver, dev); | |
8cca3548 DC |
230 | if (IS_ERR(drm)) |
231 | return PTR_ERR(drm); | |
54db5dec LS |
232 | |
233 | imxdrm = devm_kzalloc(dev, sizeof(*imxdrm), GFP_KERNEL); | |
234 | if (!imxdrm) { | |
235 | ret = -ENOMEM; | |
236 | goto err_unref; | |
237 | } | |
238 | ||
239 | imxdrm->drm = drm; | |
240 | drm->dev_private = imxdrm; | |
241 | ||
242 | /* | |
243 | * enable drm irq mode. | |
244 | * - with irq_enabled = true, we can use the vblank feature. | |
245 | * | |
246 | * P.S. note that we wouldn't use drm irq handler but | |
247 | * just specific driver own one instead because | |
248 | * drm framework supports only one irq handler and | |
249 | * drivers can well take care of their interrupts | |
250 | */ | |
251 | drm->irq_enabled = true; | |
252 | ||
253 | /* | |
254 | * set max width and height as default value(4096x4096). | |
255 | * this value would be used to check framebuffer size limitation | |
256 | * at drm_mode_addfb(). | |
257 | */ | |
f57c511a PZ |
258 | drm->mode_config.min_width = 1; |
259 | drm->mode_config.min_height = 1; | |
54db5dec LS |
260 | drm->mode_config.max_width = 4096; |
261 | drm->mode_config.max_height = 4096; | |
262 | drm->mode_config.funcs = &imx_drm_mode_config_funcs; | |
263 | drm->mode_config.helper_private = &imx_drm_mode_config_helpers; | |
264 | ||
265 | drm_mode_config_init(drm); | |
266 | ||
267 | ret = drm_vblank_init(drm, MAX_CRTC); | |
268 | if (ret) | |
269 | goto err_kms; | |
270 | ||
271 | dev_set_drvdata(dev, drm); | |
272 | ||
273 | /* Now try and bind all our sub-components */ | |
274 | ret = component_bind_all(dev, drm); | |
275 | if (ret) | |
276 | goto err_vblank; | |
277 | ||
278 | drm_mode_config_reset(drm); | |
279 | ||
280 | /* | |
281 | * All components are now initialised, so setup the fb helper. | |
282 | * The fb helper takes copies of key hardware information, so the | |
283 | * crtcs/connectors/encoders must not change after this point. | |
284 | */ | |
285 | #if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) | |
286 | if (legacyfb_depth != 16 && legacyfb_depth != 32) { | |
287 | dev_warn(dev, "Invalid legacyfb_depth. Defaulting to 16bpp\n"); | |
288 | legacyfb_depth = 16; | |
289 | } | |
e4563f6b | 290 | imxdrm->fbhelper = drm_fbdev_cma_init(drm, legacyfb_depth, MAX_CRTC); |
54db5dec LS |
291 | if (IS_ERR(imxdrm->fbhelper)) { |
292 | ret = PTR_ERR(imxdrm->fbhelper); | |
293 | imxdrm->fbhelper = NULL; | |
294 | goto err_unbind; | |
295 | } | |
296 | #endif | |
297 | ||
298 | drm_kms_helper_poll_init(drm); | |
299 | ||
300 | ret = drm_dev_register(drm, 0); | |
301 | if (ret) | |
302 | goto err_fbhelper; | |
303 | ||
304 | return 0; | |
305 | ||
306 | err_fbhelper: | |
307 | drm_kms_helper_poll_fini(drm); | |
3e3affe5 | 308 | #if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) |
54db5dec LS |
309 | if (imxdrm->fbhelper) |
310 | drm_fbdev_cma_fini(imxdrm->fbhelper); | |
311 | err_unbind: | |
3e3affe5 | 312 | #endif |
54db5dec LS |
313 | component_unbind_all(drm->dev, drm); |
314 | err_vblank: | |
315 | drm_vblank_cleanup(drm); | |
316 | err_kms: | |
317 | drm_mode_config_cleanup(drm); | |
318 | err_unref: | |
319 | drm_dev_unref(drm); | |
320 | ||
321 | return ret; | |
17b5001b RK |
322 | } |
323 | ||
324 | static void imx_drm_unbind(struct device *dev) | |
325 | { | |
54db5dec LS |
326 | struct drm_device *drm = dev_get_drvdata(dev); |
327 | struct imx_drm_device *imxdrm = drm->dev_private; | |
328 | ||
329 | drm_dev_unregister(drm); | |
330 | ||
331 | drm_kms_helper_poll_fini(drm); | |
332 | ||
333 | if (imxdrm->fbhelper) | |
334 | drm_fbdev_cma_fini(imxdrm->fbhelper); | |
335 | ||
8e3b16e2 LS |
336 | drm_mode_config_cleanup(drm); |
337 | ||
54db5dec LS |
338 | component_unbind_all(drm->dev, drm); |
339 | dev_set_drvdata(dev, NULL); | |
340 | ||
54db5dec | 341 | drm_dev_unref(drm); |
17b5001b RK |
342 | } |
343 | ||
344 | static const struct component_master_ops imx_drm_ops = { | |
17b5001b RK |
345 | .bind = imx_drm_bind, |
346 | .unbind = imx_drm_unbind, | |
347 | }; | |
348 | ||
e692da4d SH |
349 | static int imx_drm_platform_probe(struct platform_device *pdev) |
350 | { | |
9cace32f | 351 | int ret = drm_of_component_probe(&pdev->dev, compare_of, &imx_drm_ops); |
655b43cc | 352 | |
9cace32f LD |
353 | if (!ret) |
354 | ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); | |
655b43cc | 355 | |
9cace32f | 356 | return ret; |
e692da4d SH |
357 | } |
358 | ||
359 | static int imx_drm_platform_remove(struct platform_device *pdev) | |
360 | { | |
17b5001b | 361 | component_master_del(&pdev->dev, &imx_drm_ops); |
e692da4d SH |
362 | return 0; |
363 | } | |
364 | ||
bfe945c8 SG |
365 | #ifdef CONFIG_PM_SLEEP |
366 | static int imx_drm_suspend(struct device *dev) | |
367 | { | |
368 | struct drm_device *drm_dev = dev_get_drvdata(dev); | |
5f2f9115 | 369 | struct imx_drm_device *imxdrm; |
bfe945c8 SG |
370 | |
371 | /* The drm_dev is NULL before .load hook is called */ | |
372 | if (drm_dev == NULL) | |
373 | return 0; | |
374 | ||
375 | drm_kms_helper_poll_disable(drm_dev); | |
376 | ||
5f2f9115 LY |
377 | imxdrm = drm_dev->dev_private; |
378 | imxdrm->state = drm_atomic_helper_suspend(drm_dev); | |
379 | if (IS_ERR(imxdrm->state)) { | |
380 | drm_kms_helper_poll_enable(drm_dev); | |
381 | return PTR_ERR(imxdrm->state); | |
382 | } | |
383 | ||
bfe945c8 SG |
384 | return 0; |
385 | } | |
386 | ||
387 | static int imx_drm_resume(struct device *dev) | |
388 | { | |
389 | struct drm_device *drm_dev = dev_get_drvdata(dev); | |
5f2f9115 | 390 | struct imx_drm_device *imx_drm; |
bfe945c8 SG |
391 | |
392 | if (drm_dev == NULL) | |
393 | return 0; | |
394 | ||
5f2f9115 LY |
395 | imx_drm = drm_dev->dev_private; |
396 | drm_atomic_helper_resume(drm_dev, imx_drm->state); | |
bfe945c8 SG |
397 | drm_kms_helper_poll_enable(drm_dev); |
398 | ||
399 | return 0; | |
400 | } | |
401 | #endif | |
402 | ||
403 | static SIMPLE_DEV_PM_OPS(imx_drm_pm_ops, imx_drm_suspend, imx_drm_resume); | |
404 | ||
17b5001b | 405 | static const struct of_device_id imx_drm_dt_ids[] = { |
655b43cc | 406 | { .compatible = "fsl,imx-display-subsystem", }, |
17b5001b RK |
407 | { /* sentinel */ }, |
408 | }; | |
409 | MODULE_DEVICE_TABLE(of, imx_drm_dt_ids); | |
410 | ||
e692da4d SH |
411 | static struct platform_driver imx_drm_pdrv = { |
412 | .probe = imx_drm_platform_probe, | |
99c28f10 | 413 | .remove = imx_drm_platform_remove, |
e692da4d | 414 | .driver = { |
e692da4d | 415 | .name = "imx-drm", |
bfe945c8 | 416 | .pm = &imx_drm_pm_ops, |
17b5001b | 417 | .of_match_table = imx_drm_dt_ids, |
e692da4d SH |
418 | }, |
419 | }; | |
b85f2b5d | 420 | module_platform_driver(imx_drm_pdrv); |
e692da4d SH |
421 | |
422 | MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); | |
423 | MODULE_DESCRIPTION("i.MX drm driver core"); | |
424 | MODULE_LICENSE("GPL"); |