Commit | Line | Data |
---|---|---|
4bf8e196 LP |
1 | /* |
2 | * rcar_du_drv.c -- R-Car Display Unit DRM driver | |
3 | * | |
4 | * Copyright (C) 2013 Renesas Corporation | |
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> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/pm.h> | |
20 | #include <linux/slab.h> | |
21 | ||
22 | #include <drm/drmP.h> | |
23 | #include <drm/drm_crtc_helper.h> | |
24 | #include <drm/drm_gem_cma_helper.h> | |
25 | ||
26 | #include "rcar_du_crtc.h" | |
27 | #include "rcar_du_drv.h" | |
28 | #include "rcar_du_kms.h" | |
29 | #include "rcar_du_regs.h" | |
30 | ||
31 | /* ----------------------------------------------------------------------------- | |
32 | * Core device operations | |
33 | */ | |
34 | ||
35 | /* | |
36 | * rcar_du_get - Acquire a reference to the DU | |
37 | * | |
38 | * Acquiring a reference enables the device clock and setup core registers. A | |
39 | * reference must be held before accessing any hardware registers. | |
40 | * | |
41 | * This function must be called with the DRM mode_config lock held. | |
42 | * | |
43 | * Return 0 in case of success or a negative error code otherwise. | |
44 | */ | |
45 | int rcar_du_get(struct rcar_du_device *rcdu) | |
46 | { | |
47 | int ret; | |
48 | ||
49 | if (rcdu->use_count) | |
50 | goto done; | |
51 | ||
52 | /* Enable clocks before accessing the hardware. */ | |
53 | ret = clk_prepare_enable(rcdu->clock); | |
54 | if (ret < 0) | |
55 | return ret; | |
56 | ||
57 | /* Enable extended features */ | |
58 | rcar_du_write(rcdu, DEFR, DEFR_CODE | DEFR_DEFE); | |
59 | rcar_du_write(rcdu, DEFR2, DEFR2_CODE | DEFR2_DEFE2G); | |
60 | rcar_du_write(rcdu, DEFR3, DEFR3_CODE | DEFR3_DEFE3); | |
61 | rcar_du_write(rcdu, DEFR4, DEFR4_CODE); | |
62 | rcar_du_write(rcdu, DEFR5, DEFR5_CODE | DEFR5_DEFE5); | |
63 | ||
64 | /* Use DS1PR and DS2PR to configure planes priorities and connects the | |
65 | * superposition 0 to DU0 pins. DU1 pins will be configured dynamically. | |
66 | */ | |
67 | rcar_du_write(rcdu, DORCR, DORCR_PG1D_DS1 | DORCR_DPRS); | |
68 | ||
69 | done: | |
70 | rcdu->use_count++; | |
71 | return 0; | |
72 | } | |
73 | ||
74 | /* | |
75 | * rcar_du_put - Release a reference to the DU | |
76 | * | |
77 | * Releasing the last reference disables the device clock. | |
78 | * | |
79 | * This function must be called with the DRM mode_config lock held. | |
80 | */ | |
81 | void rcar_du_put(struct rcar_du_device *rcdu) | |
82 | { | |
83 | if (--rcdu->use_count) | |
84 | return; | |
85 | ||
86 | clk_disable_unprepare(rcdu->clock); | |
87 | } | |
88 | ||
89 | /* ----------------------------------------------------------------------------- | |
90 | * DRM operations | |
91 | */ | |
92 | ||
93 | static int rcar_du_unload(struct drm_device *dev) | |
94 | { | |
95 | drm_kms_helper_poll_fini(dev); | |
96 | drm_mode_config_cleanup(dev); | |
97 | drm_vblank_cleanup(dev); | |
98 | drm_irq_uninstall(dev); | |
99 | ||
100 | dev->dev_private = NULL; | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | static int rcar_du_load(struct drm_device *dev, unsigned long flags) | |
106 | { | |
107 | struct platform_device *pdev = dev->platformdev; | |
108 | struct rcar_du_platform_data *pdata = pdev->dev.platform_data; | |
109 | struct rcar_du_device *rcdu; | |
110 | struct resource *ioarea; | |
111 | struct resource *mem; | |
112 | int ret; | |
113 | ||
114 | if (pdata == NULL) { | |
115 | dev_err(dev->dev, "no platform data\n"); | |
116 | return -ENODEV; | |
117 | } | |
118 | ||
119 | rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL); | |
120 | if (rcdu == NULL) { | |
121 | dev_err(dev->dev, "failed to allocate private data\n"); | |
122 | return -ENOMEM; | |
123 | } | |
124 | ||
125 | rcdu->dev = &pdev->dev; | |
126 | rcdu->pdata = pdata; | |
127 | rcdu->ddev = dev; | |
128 | dev->dev_private = rcdu; | |
129 | ||
130 | /* I/O resources and clocks */ | |
131 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
132 | if (mem == NULL) { | |
133 | dev_err(&pdev->dev, "failed to get memory resource\n"); | |
134 | return -EINVAL; | |
135 | } | |
136 | ||
137 | ioarea = devm_request_mem_region(&pdev->dev, mem->start, | |
138 | resource_size(mem), pdev->name); | |
139 | if (ioarea == NULL) { | |
140 | dev_err(&pdev->dev, "failed to request memory region\n"); | |
141 | return -EBUSY; | |
142 | } | |
143 | ||
144 | rcdu->mmio = devm_ioremap_nocache(&pdev->dev, ioarea->start, | |
145 | resource_size(ioarea)); | |
146 | if (rcdu->mmio == NULL) { | |
147 | dev_err(&pdev->dev, "failed to remap memory resource\n"); | |
148 | return -ENOMEM; | |
149 | } | |
150 | ||
151 | rcdu->clock = devm_clk_get(&pdev->dev, NULL); | |
152 | if (IS_ERR(rcdu->clock)) { | |
153 | dev_err(&pdev->dev, "failed to get clock\n"); | |
154 | return -ENOENT; | |
155 | } | |
156 | ||
157 | /* DRM/KMS objects */ | |
158 | ret = rcar_du_modeset_init(rcdu); | |
159 | if (ret < 0) { | |
160 | dev_err(&pdev->dev, "failed to initialize DRM/KMS\n"); | |
161 | goto done; | |
162 | } | |
163 | ||
164 | /* IRQ and vblank handling */ | |
165 | ret = drm_vblank_init(dev, (1 << rcdu->num_crtcs) - 1); | |
166 | if (ret < 0) { | |
167 | dev_err(&pdev->dev, "failed to initialize vblank\n"); | |
168 | goto done; | |
169 | } | |
170 | ||
171 | ret = drm_irq_install(dev); | |
172 | if (ret < 0) { | |
173 | dev_err(&pdev->dev, "failed to install IRQ handler\n"); | |
174 | goto done; | |
175 | } | |
176 | ||
177 | platform_set_drvdata(pdev, rcdu); | |
178 | ||
179 | done: | |
180 | if (ret) | |
181 | rcar_du_unload(dev); | |
182 | ||
183 | return ret; | |
184 | } | |
185 | ||
186 | static void rcar_du_preclose(struct drm_device *dev, struct drm_file *file) | |
187 | { | |
188 | struct rcar_du_device *rcdu = dev->dev_private; | |
189 | unsigned int i; | |
190 | ||
191 | for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i) | |
192 | rcar_du_crtc_cancel_page_flip(&rcdu->crtcs[i], file); | |
193 | } | |
194 | ||
195 | static irqreturn_t rcar_du_irq(int irq, void *arg) | |
196 | { | |
197 | struct drm_device *dev = arg; | |
198 | struct rcar_du_device *rcdu = dev->dev_private; | |
199 | unsigned int i; | |
200 | ||
201 | for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i) | |
202 | rcar_du_crtc_irq(&rcdu->crtcs[i]); | |
203 | ||
204 | return IRQ_HANDLED; | |
205 | } | |
206 | ||
207 | static int rcar_du_enable_vblank(struct drm_device *dev, int crtc) | |
208 | { | |
209 | struct rcar_du_device *rcdu = dev->dev_private; | |
210 | ||
211 | rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], true); | |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
216 | static void rcar_du_disable_vblank(struct drm_device *dev, int crtc) | |
217 | { | |
218 | struct rcar_du_device *rcdu = dev->dev_private; | |
219 | ||
220 | rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], false); | |
221 | } | |
222 | ||
223 | static const struct file_operations rcar_du_fops = { | |
224 | .owner = THIS_MODULE, | |
225 | .open = drm_open, | |
226 | .release = drm_release, | |
227 | .unlocked_ioctl = drm_ioctl, | |
228 | #ifdef CONFIG_COMPAT | |
229 | .compat_ioctl = drm_compat_ioctl, | |
230 | #endif | |
231 | .poll = drm_poll, | |
232 | .read = drm_read, | |
233 | .fasync = drm_fasync, | |
234 | .llseek = no_llseek, | |
235 | .mmap = drm_gem_cma_mmap, | |
236 | }; | |
237 | ||
238 | static struct drm_driver rcar_du_driver = { | |
239 | .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET | |
240 | | DRIVER_PRIME, | |
241 | .load = rcar_du_load, | |
242 | .unload = rcar_du_unload, | |
243 | .preclose = rcar_du_preclose, | |
244 | .irq_handler = rcar_du_irq, | |
245 | .get_vblank_counter = drm_vblank_count, | |
246 | .enable_vblank = rcar_du_enable_vblank, | |
247 | .disable_vblank = rcar_du_disable_vblank, | |
248 | .gem_free_object = drm_gem_cma_free_object, | |
249 | .gem_vm_ops = &drm_gem_cma_vm_ops, | |
250 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | |
251 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
252 | .gem_prime_import = drm_gem_cma_dmabuf_import, | |
253 | .gem_prime_export = drm_gem_cma_dmabuf_export, | |
59e32642 | 254 | .dumb_create = rcar_du_dumb_create, |
4bf8e196 LP |
255 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, |
256 | .dumb_destroy = drm_gem_cma_dumb_destroy, | |
257 | .fops = &rcar_du_fops, | |
258 | .name = "rcar-du", | |
259 | .desc = "Renesas R-Car Display Unit", | |
260 | .date = "20130110", | |
261 | .major = 1, | |
262 | .minor = 0, | |
263 | }; | |
264 | ||
265 | /* ----------------------------------------------------------------------------- | |
266 | * Power management | |
267 | */ | |
268 | ||
269 | #if CONFIG_PM_SLEEP | |
270 | static int rcar_du_pm_suspend(struct device *dev) | |
271 | { | |
272 | struct rcar_du_device *rcdu = dev_get_drvdata(dev); | |
273 | ||
274 | drm_kms_helper_poll_disable(rcdu->ddev); | |
275 | /* TODO Suspend the CRTC */ | |
276 | ||
277 | return 0; | |
278 | } | |
279 | ||
280 | static int rcar_du_pm_resume(struct device *dev) | |
281 | { | |
282 | struct rcar_du_device *rcdu = dev_get_drvdata(dev); | |
283 | ||
284 | /* TODO Resume the CRTC */ | |
285 | ||
286 | drm_kms_helper_poll_enable(rcdu->ddev); | |
287 | return 0; | |
288 | } | |
289 | #endif | |
290 | ||
291 | static const struct dev_pm_ops rcar_du_pm_ops = { | |
292 | SET_SYSTEM_SLEEP_PM_OPS(rcar_du_pm_suspend, rcar_du_pm_resume) | |
293 | }; | |
294 | ||
295 | /* ----------------------------------------------------------------------------- | |
296 | * Platform driver | |
297 | */ | |
298 | ||
299 | static int rcar_du_probe(struct platform_device *pdev) | |
300 | { | |
301 | return drm_platform_init(&rcar_du_driver, pdev); | |
302 | } | |
303 | ||
304 | static int rcar_du_remove(struct platform_device *pdev) | |
305 | { | |
306 | drm_platform_exit(&rcar_du_driver, pdev); | |
307 | ||
308 | return 0; | |
309 | } | |
310 | ||
311 | static struct platform_driver rcar_du_platform_driver = { | |
312 | .probe = rcar_du_probe, | |
313 | .remove = rcar_du_remove, | |
314 | .driver = { | |
315 | .owner = THIS_MODULE, | |
316 | .name = "rcar-du", | |
317 | .pm = &rcar_du_pm_ops, | |
318 | }, | |
319 | }; | |
320 | ||
321 | module_platform_driver(rcar_du_platform_driver); | |
322 | ||
323 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); | |
324 | MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); | |
325 | MODULE_LICENSE("GPL"); |