Commit | Line | Data |
---|---|---|
4bf8e196 LP |
1 | /* |
2 | * rcar_du_drv.c -- R-Car Display Unit DRM driver | |
3 | * | |
2427b303 | 4 | * Copyright (C) 2013-2015 Renesas Electronics Corporation |
4bf8e196 LP |
5 | * |
6 | * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | */ | |
13 | ||
14 | #include <linux/clk.h> | |
15 | #include <linux/io.h> | |
16 | #include <linux/mm.h> | |
17 | #include <linux/module.h> | |
96c02691 | 18 | #include <linux/of_device.h> |
4bf8e196 LP |
19 | #include <linux/platform_device.h> |
20 | #include <linux/pm.h> | |
21 | #include <linux/slab.h> | |
8d3f9b22 | 22 | #include <linux/wait.h> |
4bf8e196 LP |
23 | |
24 | #include <drm/drmP.h> | |
25 | #include <drm/drm_crtc_helper.h> | |
3864c6f4 | 26 | #include <drm/drm_fb_cma_helper.h> |
4bf8e196 LP |
27 | #include <drm/drm_gem_cma_helper.h> |
28 | ||
29 | #include "rcar_du_crtc.h" | |
30 | #include "rcar_du_drv.h" | |
31 | #include "rcar_du_kms.h" | |
32 | #include "rcar_du_regs.h" | |
33 | ||
96c02691 LP |
34 | /* ----------------------------------------------------------------------------- |
35 | * Device Information | |
36 | */ | |
37 | ||
38 | static const struct rcar_du_device_info rcar_du_r8a7779_info = { | |
2427b303 | 39 | .gen = 2, |
96c02691 LP |
40 | .features = 0, |
41 | .num_crtcs = 2, | |
42 | .routes = { | |
43 | /* R8A7779 has two RGB outputs and one (currently unsupported) | |
44 | * TCON output. | |
45 | */ | |
46 | [RCAR_DU_OUTPUT_DPAD0] = { | |
47 | .possible_crtcs = BIT(0), | |
48 | .encoder_type = DRM_MODE_ENCODER_NONE, | |
49 | .port = 0, | |
50 | }, | |
51 | [RCAR_DU_OUTPUT_DPAD1] = { | |
52 | .possible_crtcs = BIT(1) | BIT(0), | |
53 | .encoder_type = DRM_MODE_ENCODER_NONE, | |
54 | .port = 1, | |
55 | }, | |
56 | }, | |
57 | .num_lvds = 0, | |
58 | }; | |
59 | ||
60 | static const struct rcar_du_device_info rcar_du_r8a7790_info = { | |
2427b303 | 61 | .gen = 2, |
0c1c8776 LP |
62 | .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK |
63 | | RCAR_DU_FEATURE_EXT_CTRL_REGS, | |
96c02691 LP |
64 | .quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES, |
65 | .num_crtcs = 3, | |
66 | .routes = { | |
67 | /* R8A7790 has one RGB output, two LVDS outputs and one | |
68 | * (currently unsupported) TCON output. | |
69 | */ | |
70 | [RCAR_DU_OUTPUT_DPAD0] = { | |
71 | .possible_crtcs = BIT(2) | BIT(1) | BIT(0), | |
72 | .encoder_type = DRM_MODE_ENCODER_NONE, | |
73 | .port = 0, | |
74 | }, | |
75 | [RCAR_DU_OUTPUT_LVDS0] = { | |
76 | .possible_crtcs = BIT(0), | |
77 | .encoder_type = DRM_MODE_ENCODER_LVDS, | |
78 | .port = 1, | |
79 | }, | |
80 | [RCAR_DU_OUTPUT_LVDS1] = { | |
81 | .possible_crtcs = BIT(2) | BIT(1), | |
82 | .encoder_type = DRM_MODE_ENCODER_LVDS, | |
83 | .port = 2, | |
84 | }, | |
85 | }, | |
86 | .num_lvds = 2, | |
87 | }; | |
88 | ||
f1ceb84a | 89 | /* M2-W (r8a7791) and M2-N (r8a7793) are identical */ |
96c02691 | 90 | static const struct rcar_du_device_info rcar_du_r8a7791_info = { |
2427b303 | 91 | .gen = 2, |
0c1c8776 LP |
92 | .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK |
93 | | RCAR_DU_FEATURE_EXT_CTRL_REGS, | |
96c02691 LP |
94 | .num_crtcs = 2, |
95 | .routes = { | |
f1ceb84a | 96 | /* R8A779[13] has one RGB output, one LVDS output and one |
96c02691 LP |
97 | * (currently unsupported) TCON output. |
98 | */ | |
99 | [RCAR_DU_OUTPUT_DPAD0] = { | |
f4f0fb79 | 100 | .possible_crtcs = BIT(1) | BIT(0), |
96c02691 LP |
101 | .encoder_type = DRM_MODE_ENCODER_NONE, |
102 | .port = 0, | |
103 | }, | |
104 | [RCAR_DU_OUTPUT_LVDS0] = { | |
105 | .possible_crtcs = BIT(0), | |
106 | .encoder_type = DRM_MODE_ENCODER_LVDS, | |
107 | .port = 1, | |
108 | }, | |
109 | }, | |
110 | .num_lvds = 1, | |
111 | }; | |
112 | ||
090425c4 | 113 | static const struct rcar_du_device_info rcar_du_r8a7794_info = { |
2427b303 | 114 | .gen = 2, |
090425c4 LP |
115 | .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK |
116 | | RCAR_DU_FEATURE_EXT_CTRL_REGS, | |
117 | .num_crtcs = 2, | |
118 | .routes = { | |
119 | /* R8A7794 has two RGB outputs and one (currently unsupported) | |
120 | * TCON output. | |
121 | */ | |
122 | [RCAR_DU_OUTPUT_DPAD0] = { | |
123 | .possible_crtcs = BIT(0), | |
124 | .encoder_type = DRM_MODE_ENCODER_NONE, | |
125 | .port = 0, | |
126 | }, | |
127 | [RCAR_DU_OUTPUT_DPAD1] = { | |
128 | .possible_crtcs = BIT(1), | |
129 | .encoder_type = DRM_MODE_ENCODER_NONE, | |
130 | .port = 1, | |
131 | }, | |
132 | }, | |
133 | .num_lvds = 0, | |
134 | }; | |
135 | ||
2427b303 LP |
136 | static const struct rcar_du_device_info rcar_du_r8a7795_info = { |
137 | .gen = 3, | |
138 | .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK | |
139 | | RCAR_DU_FEATURE_EXT_CTRL_REGS | |
140 | | RCAR_DU_FEATURE_VSP1_SOURCE, | |
141 | .num_crtcs = 4, | |
142 | .routes = { | |
6bc2e15c KM |
143 | /* R8A7795 has one RGB output, one LVDS output and two |
144 | * (currently unsupported) HDMI outputs. | |
2427b303 LP |
145 | */ |
146 | [RCAR_DU_OUTPUT_DPAD0] = { | |
147 | .possible_crtcs = BIT(3), | |
148 | .encoder_type = DRM_MODE_ENCODER_NONE, | |
149 | .port = 0, | |
150 | }, | |
6bc2e15c KM |
151 | [RCAR_DU_OUTPUT_LVDS0] = { |
152 | .possible_crtcs = BIT(0), | |
153 | .encoder_type = DRM_MODE_ENCODER_LVDS, | |
154 | .port = 3, | |
155 | }, | |
2427b303 | 156 | }, |
6bc2e15c | 157 | .num_lvds = 1, |
2427b303 LP |
158 | }; |
159 | ||
96c02691 LP |
160 | static const struct of_device_id rcar_du_of_table[] = { |
161 | { .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info }, | |
162 | { .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info }, | |
163 | { .compatible = "renesas,du-r8a7791", .data = &rcar_du_r8a7791_info }, | |
f1ceb84a | 164 | { .compatible = "renesas,du-r8a7793", .data = &rcar_du_r8a7791_info }, |
090425c4 | 165 | { .compatible = "renesas,du-r8a7794", .data = &rcar_du_r8a7794_info }, |
2427b303 | 166 | { .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info }, |
96c02691 LP |
167 | { } |
168 | }; | |
169 | ||
170 | MODULE_DEVICE_TABLE(of, rcar_du_of_table); | |
171 | ||
4bf8e196 LP |
172 | /* ----------------------------------------------------------------------------- |
173 | * DRM operations | |
174 | */ | |
175 | ||
3864c6f4 | 176 | static void rcar_du_lastclose(struct drm_device *dev) |
4bf8e196 | 177 | { |
4bf8e196 | 178 | struct rcar_du_device *rcdu = dev->dev_private; |
4bf8e196 | 179 | |
3864c6f4 | 180 | drm_fbdev_cma_restore_mode(rcdu->fbdev); |
4bf8e196 LP |
181 | } |
182 | ||
88e72717 | 183 | static int rcar_du_enable_vblank(struct drm_device *dev, unsigned int pipe) |
4bf8e196 LP |
184 | { |
185 | struct rcar_du_device *rcdu = dev->dev_private; | |
186 | ||
88e72717 | 187 | rcar_du_crtc_enable_vblank(&rcdu->crtcs[pipe], true); |
4bf8e196 LP |
188 | |
189 | return 0; | |
190 | } | |
191 | ||
88e72717 | 192 | static void rcar_du_disable_vblank(struct drm_device *dev, unsigned int pipe) |
4bf8e196 LP |
193 | { |
194 | struct rcar_du_device *rcdu = dev->dev_private; | |
195 | ||
88e72717 | 196 | rcar_du_crtc_enable_vblank(&rcdu->crtcs[pipe], false); |
4bf8e196 LP |
197 | } |
198 | ||
199 | static const struct file_operations rcar_du_fops = { | |
200 | .owner = THIS_MODULE, | |
201 | .open = drm_open, | |
202 | .release = drm_release, | |
203 | .unlocked_ioctl = drm_ioctl, | |
204 | #ifdef CONFIG_COMPAT | |
205 | .compat_ioctl = drm_compat_ioctl, | |
206 | #endif | |
207 | .poll = drm_poll, | |
208 | .read = drm_read, | |
4bf8e196 LP |
209 | .llseek = no_llseek, |
210 | .mmap = drm_gem_cma_mmap, | |
211 | }; | |
212 | ||
213 | static struct drm_driver rcar_du_driver = { | |
6dbe686b LP |
214 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
215 | | DRIVER_ATOMIC, | |
3864c6f4 | 216 | .lastclose = rcar_du_lastclose, |
b44f8408 | 217 | .get_vblank_counter = drm_vblank_no_hw_counter, |
4bf8e196 LP |
218 | .enable_vblank = rcar_du_enable_vblank, |
219 | .disable_vblank = rcar_du_disable_vblank, | |
220 | .gem_free_object = drm_gem_cma_free_object, | |
221 | .gem_vm_ops = &drm_gem_cma_vm_ops, | |
222 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | |
223 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
ffb40400 LP |
224 | .gem_prime_import = drm_gem_prime_import, |
225 | .gem_prime_export = drm_gem_prime_export, | |
226 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | |
227 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | |
228 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | |
229 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | |
230 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | |
59e32642 | 231 | .dumb_create = rcar_du_dumb_create, |
4bf8e196 | 232 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, |
43387b37 | 233 | .dumb_destroy = drm_gem_dumb_destroy, |
4bf8e196 LP |
234 | .fops = &rcar_du_fops, |
235 | .name = "rcar-du", | |
236 | .desc = "Renesas R-Car Display Unit", | |
237 | .date = "20130110", | |
238 | .major = 1, | |
239 | .minor = 0, | |
240 | }; | |
241 | ||
242 | /* ----------------------------------------------------------------------------- | |
243 | * Power management | |
244 | */ | |
245 | ||
396d7a24 | 246 | #ifdef CONFIG_PM_SLEEP |
4bf8e196 LP |
247 | static int rcar_du_pm_suspend(struct device *dev) |
248 | { | |
249 | struct rcar_du_device *rcdu = dev_get_drvdata(dev); | |
250 | ||
251 | drm_kms_helper_poll_disable(rcdu->ddev); | |
252 | /* TODO Suspend the CRTC */ | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | static int rcar_du_pm_resume(struct device *dev) | |
258 | { | |
259 | struct rcar_du_device *rcdu = dev_get_drvdata(dev); | |
260 | ||
261 | /* TODO Resume the CRTC */ | |
262 | ||
263 | drm_kms_helper_poll_enable(rcdu->ddev); | |
264 | return 0; | |
265 | } | |
266 | #endif | |
267 | ||
268 | static const struct dev_pm_ops rcar_du_pm_ops = { | |
269 | SET_SYSTEM_SLEEP_PM_OPS(rcar_du_pm_suspend, rcar_du_pm_resume) | |
270 | }; | |
271 | ||
272 | /* ----------------------------------------------------------------------------- | |
273 | * Platform driver | |
274 | */ | |
275 | ||
c1d4b38c | 276 | static int rcar_du_remove(struct platform_device *pdev) |
4bf8e196 | 277 | { |
c1d4b38c LP |
278 | struct rcar_du_device *rcdu = platform_get_drvdata(pdev); |
279 | struct drm_device *ddev = rcdu->ddev; | |
280 | ||
6c87e5c3 | 281 | drm_connector_unregister_all(ddev); |
c1d4b38c LP |
282 | drm_dev_unregister(ddev); |
283 | ||
284 | if (rcdu->fbdev) | |
285 | drm_fbdev_cma_fini(rcdu->fbdev); | |
286 | ||
287 | drm_kms_helper_poll_fini(ddev); | |
288 | drm_mode_config_cleanup(ddev); | |
289 | drm_vblank_cleanup(ddev); | |
290 | ||
291 | drm_dev_unref(ddev); | |
292 | ||
293 | return 0; | |
4bf8e196 LP |
294 | } |
295 | ||
c1d4b38c | 296 | static int rcar_du_probe(struct platform_device *pdev) |
4bf8e196 | 297 | { |
c1d4b38c LP |
298 | struct device_node *np = pdev->dev.of_node; |
299 | struct rcar_du_device *rcdu; | |
c1d4b38c LP |
300 | struct drm_device *ddev; |
301 | struct resource *mem; | |
302 | int ret; | |
303 | ||
304 | if (np == NULL) { | |
305 | dev_err(&pdev->dev, "no device tree node\n"); | |
306 | return -ENODEV; | |
307 | } | |
308 | ||
309 | /* Allocate and initialize the DRM and R-Car device structures. */ | |
310 | rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL); | |
311 | if (rcdu == NULL) | |
312 | return -ENOMEM; | |
313 | ||
314 | init_waitqueue_head(&rcdu->commit.wait); | |
315 | ||
316 | rcdu->dev = &pdev->dev; | |
317 | rcdu->info = of_match_device(rcar_du_of_table, rcdu->dev)->data; | |
318 | ||
319 | ddev = drm_dev_alloc(&rcar_du_driver, &pdev->dev); | |
320 | if (!ddev) | |
321 | return -ENOMEM; | |
322 | ||
323 | drm_dev_set_unique(ddev, dev_name(&pdev->dev)); | |
324 | ||
325 | rcdu->ddev = ddev; | |
326 | ddev->dev_private = rcdu; | |
57a24cf8 | 327 | |
c1d4b38c LP |
328 | platform_set_drvdata(pdev, rcdu); |
329 | ||
330 | /* I/O resources */ | |
331 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
332 | rcdu->mmio = devm_ioremap_resource(&pdev->dev, mem); | |
333 | if (IS_ERR(rcdu->mmio)) { | |
334 | ret = PTR_ERR(rcdu->mmio); | |
335 | goto error; | |
336 | } | |
337 | ||
338 | /* Initialize vertical blanking interrupts handling. Start with vblank | |
339 | * disabled for all CRTCs. | |
340 | */ | |
341 | ret = drm_vblank_init(ddev, (1 << rcdu->info->num_crtcs) - 1); | |
342 | if (ret < 0) { | |
343 | dev_err(&pdev->dev, "failed to initialize vblank\n"); | |
344 | goto error; | |
345 | } | |
346 | ||
347 | /* DRM/KMS objects */ | |
348 | ret = rcar_du_modeset_init(rcdu); | |
349 | if (ret < 0) { | |
350 | dev_err(&pdev->dev, "failed to initialize DRM/KMS (%d)\n", ret); | |
351 | goto error; | |
352 | } | |
353 | ||
354 | ddev->irq_enabled = 1; | |
355 | ||
356 | /* Register the DRM device with the core and the connectors with | |
357 | * sysfs. | |
358 | */ | |
359 | ret = drm_dev_register(ddev, 0); | |
360 | if (ret) | |
361 | goto error; | |
362 | ||
d63c25e4 | 363 | ret = drm_connector_register_all(ddev); |
c1d4b38c LP |
364 | if (ret < 0) |
365 | goto error; | |
366 | ||
367 | DRM_INFO("Device %s probed\n", dev_name(&pdev->dev)); | |
4bf8e196 LP |
368 | |
369 | return 0; | |
c1d4b38c LP |
370 | |
371 | error: | |
372 | rcar_du_remove(pdev); | |
373 | ||
374 | return ret; | |
4bf8e196 LP |
375 | } |
376 | ||
377 | static struct platform_driver rcar_du_platform_driver = { | |
378 | .probe = rcar_du_probe, | |
379 | .remove = rcar_du_remove, | |
380 | .driver = { | |
4bf8e196 LP |
381 | .name = "rcar-du", |
382 | .pm = &rcar_du_pm_ops, | |
96c02691 | 383 | .of_match_table = rcar_du_of_table, |
4bf8e196 LP |
384 | }, |
385 | }; | |
386 | ||
387 | module_platform_driver(rcar_du_platform_driver); | |
388 | ||
389 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); | |
390 | MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); | |
391 | MODULE_LICENSE("GPL"); |