Commit | Line | Data |
---|---|---|
51c13278 LP |
1 | /* |
2 | * shmob_drm_drv.c -- SH Mobile DRM driver | |
3 | * | |
9588b826 | 4 | * Copyright (C) 2012 Renesas Electronics Corporation |
51c13278 LP |
5 | * |
6 | * 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 "shmob_drm_crtc.h" | |
27 | #include "shmob_drm_drv.h" | |
28 | #include "shmob_drm_kms.h" | |
29 | #include "shmob_drm_plane.h" | |
30 | #include "shmob_drm_regs.h" | |
31 | ||
32 | /* ----------------------------------------------------------------------------- | |
33 | * Hardware initialization | |
34 | */ | |
35 | ||
56550d94 | 36 | static int shmob_drm_init_interface(struct shmob_drm_device *sdev) |
51c13278 LP |
37 | { |
38 | static const u32 ldmt1r[] = { | |
39 | [SHMOB_DRM_IFACE_RGB8] = LDMT1R_MIFTYP_RGB8, | |
40 | [SHMOB_DRM_IFACE_RGB9] = LDMT1R_MIFTYP_RGB9, | |
41 | [SHMOB_DRM_IFACE_RGB12A] = LDMT1R_MIFTYP_RGB12A, | |
42 | [SHMOB_DRM_IFACE_RGB12B] = LDMT1R_MIFTYP_RGB12B, | |
43 | [SHMOB_DRM_IFACE_RGB16] = LDMT1R_MIFTYP_RGB16, | |
44 | [SHMOB_DRM_IFACE_RGB18] = LDMT1R_MIFTYP_RGB18, | |
45 | [SHMOB_DRM_IFACE_RGB24] = LDMT1R_MIFTYP_RGB24, | |
46 | [SHMOB_DRM_IFACE_YUV422] = LDMT1R_MIFTYP_YCBCR, | |
47 | [SHMOB_DRM_IFACE_SYS8A] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8A, | |
48 | [SHMOB_DRM_IFACE_SYS8B] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8B, | |
49 | [SHMOB_DRM_IFACE_SYS8C] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8C, | |
50 | [SHMOB_DRM_IFACE_SYS8D] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8D, | |
51 | [SHMOB_DRM_IFACE_SYS9] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS9, | |
52 | [SHMOB_DRM_IFACE_SYS12] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS12, | |
53 | [SHMOB_DRM_IFACE_SYS16A] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16A, | |
54 | [SHMOB_DRM_IFACE_SYS16B] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16B, | |
55 | [SHMOB_DRM_IFACE_SYS16C] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16C, | |
56 | [SHMOB_DRM_IFACE_SYS18] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS18, | |
57 | [SHMOB_DRM_IFACE_SYS24] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS24, | |
58 | }; | |
59 | ||
60 | if (sdev->pdata->iface.interface >= ARRAY_SIZE(ldmt1r)) { | |
61 | dev_err(sdev->dev, "invalid interface type %u\n", | |
62 | sdev->pdata->iface.interface); | |
63 | return -EINVAL; | |
64 | } | |
65 | ||
66 | sdev->ldmt1r = ldmt1r[sdev->pdata->iface.interface]; | |
67 | return 0; | |
68 | } | |
69 | ||
56550d94 | 70 | static int shmob_drm_setup_clocks(struct shmob_drm_device *sdev, |
51c13278 LP |
71 | enum shmob_drm_clk_source clksrc) |
72 | { | |
73 | struct clk *clk; | |
74 | char *clkname; | |
75 | ||
76 | switch (clksrc) { | |
77 | case SHMOB_DRM_CLK_BUS: | |
78 | clkname = "bus_clk"; | |
79 | sdev->lddckr = LDDCKR_ICKSEL_BUS; | |
80 | break; | |
81 | case SHMOB_DRM_CLK_PERIPHERAL: | |
82 | clkname = "peripheral_clk"; | |
83 | sdev->lddckr = LDDCKR_ICKSEL_MIPI; | |
84 | break; | |
85 | case SHMOB_DRM_CLK_EXTERNAL: | |
86 | clkname = NULL; | |
87 | sdev->lddckr = LDDCKR_ICKSEL_HDMI; | |
88 | break; | |
89 | default: | |
90 | return -EINVAL; | |
91 | } | |
92 | ||
16ad3b2c | 93 | clk = devm_clk_get(sdev->dev, clkname); |
51c13278 LP |
94 | if (IS_ERR(clk)) { |
95 | dev_err(sdev->dev, "cannot get dot clock %s\n", clkname); | |
96 | return PTR_ERR(clk); | |
97 | } | |
98 | ||
99 | sdev->clock = clk; | |
100 | return 0; | |
101 | } | |
102 | ||
103 | /* ----------------------------------------------------------------------------- | |
104 | * DRM operations | |
105 | */ | |
106 | ||
107 | static int shmob_drm_unload(struct drm_device *dev) | |
108 | { | |
51c13278 LP |
109 | drm_kms_helper_poll_fini(dev); |
110 | drm_mode_config_cleanup(dev); | |
111 | drm_vblank_cleanup(dev); | |
112 | drm_irq_uninstall(dev); | |
113 | ||
51c13278 | 114 | dev->dev_private = NULL; |
51c13278 LP |
115 | |
116 | return 0; | |
117 | } | |
118 | ||
119 | static int shmob_drm_load(struct drm_device *dev, unsigned long flags) | |
120 | { | |
121 | struct shmob_drm_platform_data *pdata = dev->dev->platform_data; | |
122 | struct platform_device *pdev = dev->platformdev; | |
123 | struct shmob_drm_device *sdev; | |
124 | struct resource *res; | |
125 | unsigned int i; | |
126 | int ret; | |
127 | ||
128 | if (pdata == NULL) { | |
129 | dev_err(dev->dev, "no platform data\n"); | |
130 | return -EINVAL; | |
131 | } | |
132 | ||
16ad3b2c | 133 | sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); |
51c13278 LP |
134 | if (sdev == NULL) { |
135 | dev_err(dev->dev, "failed to allocate private data\n"); | |
136 | return -ENOMEM; | |
137 | } | |
138 | ||
139 | sdev->dev = &pdev->dev; | |
140 | sdev->pdata = pdata; | |
141 | spin_lock_init(&sdev->irq_lock); | |
142 | ||
143 | sdev->ddev = dev; | |
144 | dev->dev_private = sdev; | |
145 | ||
146 | /* I/O resources and clocks */ | |
147 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
148 | if (res == NULL) { | |
149 | dev_err(&pdev->dev, "failed to get memory resource\n"); | |
16ad3b2c | 150 | return -EINVAL; |
51c13278 LP |
151 | } |
152 | ||
16ad3b2c LP |
153 | sdev->mmio = devm_ioremap_nocache(&pdev->dev, res->start, |
154 | resource_size(res)); | |
51c13278 LP |
155 | if (sdev->mmio == NULL) { |
156 | dev_err(&pdev->dev, "failed to remap memory resource\n"); | |
16ad3b2c | 157 | return -ENOMEM; |
51c13278 LP |
158 | } |
159 | ||
160 | ret = shmob_drm_setup_clocks(sdev, pdata->clk_source); | |
161 | if (ret < 0) | |
16ad3b2c | 162 | return ret; |
51c13278 LP |
163 | |
164 | ret = shmob_drm_init_interface(sdev); | |
165 | if (ret < 0) | |
16ad3b2c | 166 | return ret; |
51c13278 LP |
167 | |
168 | ret = shmob_drm_modeset_init(sdev); | |
169 | if (ret < 0) { | |
170 | dev_err(&pdev->dev, "failed to initialize mode setting\n"); | |
16ad3b2c | 171 | return ret; |
51c13278 LP |
172 | } |
173 | ||
174 | for (i = 0; i < 4; ++i) { | |
175 | ret = shmob_drm_plane_create(sdev, i); | |
176 | if (ret < 0) { | |
177 | dev_err(&pdev->dev, "failed to create plane %u\n", i); | |
178 | goto done; | |
179 | } | |
180 | } | |
181 | ||
182 | ret = drm_vblank_init(dev, 1); | |
183 | if (ret < 0) { | |
184 | dev_err(&pdev->dev, "failed to initialize vblank\n"); | |
185 | goto done; | |
186 | } | |
187 | ||
bb0f1b5c | 188 | ret = drm_irq_install(dev, platform_get_irq(dev->platformdev, 0)); |
51c13278 LP |
189 | if (ret < 0) { |
190 | dev_err(&pdev->dev, "failed to install IRQ handler\n"); | |
191 | goto done; | |
192 | } | |
193 | ||
a16d4f86 TR |
194 | platform_set_drvdata(pdev, sdev); |
195 | ||
51c13278 LP |
196 | done: |
197 | if (ret) | |
198 | shmob_drm_unload(dev); | |
199 | ||
200 | return ret; | |
201 | } | |
202 | ||
51c13278 LP |
203 | static irqreturn_t shmob_drm_irq(int irq, void *arg) |
204 | { | |
205 | struct drm_device *dev = arg; | |
206 | struct shmob_drm_device *sdev = dev->dev_private; | |
207 | unsigned long flags; | |
208 | u32 status; | |
209 | ||
210 | /* Acknowledge interrupts. Putting interrupt enable and interrupt flag | |
211 | * bits in the same register is really brain-dead design and requires | |
212 | * taking a spinlock. | |
213 | */ | |
214 | spin_lock_irqsave(&sdev->irq_lock, flags); | |
215 | status = lcdc_read(sdev, LDINTR); | |
216 | lcdc_write(sdev, LDINTR, status ^ LDINTR_STATUS_MASK); | |
217 | spin_unlock_irqrestore(&sdev->irq_lock, flags); | |
218 | ||
219 | if (status & LDINTR_VES) { | |
220 | drm_handle_vblank(dev, 0); | |
221 | shmob_drm_crtc_finish_page_flip(&sdev->crtc); | |
222 | } | |
223 | ||
224 | return IRQ_HANDLED; | |
225 | } | |
226 | ||
88e72717 | 227 | static int shmob_drm_enable_vblank(struct drm_device *dev, unsigned int pipe) |
51c13278 LP |
228 | { |
229 | struct shmob_drm_device *sdev = dev->dev_private; | |
230 | ||
231 | shmob_drm_crtc_enable_vblank(sdev, true); | |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
88e72717 | 236 | static void shmob_drm_disable_vblank(struct drm_device *dev, unsigned int pipe) |
51c13278 LP |
237 | { |
238 | struct shmob_drm_device *sdev = dev->dev_private; | |
239 | ||
240 | shmob_drm_crtc_enable_vblank(sdev, false); | |
241 | } | |
242 | ||
243 | static const struct file_operations shmob_drm_fops = { | |
244 | .owner = THIS_MODULE, | |
245 | .open = drm_open, | |
246 | .release = drm_release, | |
247 | .unlocked_ioctl = drm_ioctl, | |
248 | #ifdef CONFIG_COMPAT | |
249 | .compat_ioctl = drm_compat_ioctl, | |
250 | #endif | |
251 | .poll = drm_poll, | |
252 | .read = drm_read, | |
51c13278 LP |
253 | .llseek = no_llseek, |
254 | .mmap = drm_gem_cma_mmap, | |
255 | }; | |
256 | ||
257 | static struct drm_driver shmob_drm_driver = { | |
416c3900 LP |
258 | .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET |
259 | | DRIVER_PRIME, | |
51c13278 LP |
260 | .load = shmob_drm_load, |
261 | .unload = shmob_drm_unload, | |
915b4d11 | 262 | .set_busid = drm_platform_set_busid, |
51c13278 | 263 | .irq_handler = shmob_drm_irq, |
b44f8408 | 264 | .get_vblank_counter = drm_vblank_no_hw_counter, |
51c13278 LP |
265 | .enable_vblank = shmob_drm_enable_vblank, |
266 | .disable_vblank = shmob_drm_disable_vblank, | |
267 | .gem_free_object = drm_gem_cma_free_object, | |
268 | .gem_vm_ops = &drm_gem_cma_vm_ops, | |
416c3900 LP |
269 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, |
270 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
58cbd3ac LP |
271 | .gem_prime_import = drm_gem_prime_import, |
272 | .gem_prime_export = drm_gem_prime_export, | |
273 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | |
274 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | |
275 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | |
276 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | |
277 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | |
51c13278 LP |
278 | .dumb_create = drm_gem_cma_dumb_create, |
279 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, | |
43387b37 | 280 | .dumb_destroy = drm_gem_dumb_destroy, |
51c13278 LP |
281 | .fops = &shmob_drm_fops, |
282 | .name = "shmob-drm", | |
283 | .desc = "Renesas SH Mobile DRM", | |
284 | .date = "20120424", | |
285 | .major = 1, | |
286 | .minor = 0, | |
287 | }; | |
288 | ||
289 | /* ----------------------------------------------------------------------------- | |
290 | * Power management | |
291 | */ | |
292 | ||
681ff260 | 293 | #ifdef CONFIG_PM_SLEEP |
51c13278 LP |
294 | static int shmob_drm_pm_suspend(struct device *dev) |
295 | { | |
a16d4f86 | 296 | struct shmob_drm_device *sdev = dev_get_drvdata(dev); |
51c13278 | 297 | |
a16d4f86 | 298 | drm_kms_helper_poll_disable(sdev->ddev); |
51c13278 LP |
299 | shmob_drm_crtc_suspend(&sdev->crtc); |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
304 | static int shmob_drm_pm_resume(struct device *dev) | |
305 | { | |
a16d4f86 | 306 | struct shmob_drm_device *sdev = dev_get_drvdata(dev); |
51c13278 | 307 | |
b13f5980 | 308 | drm_modeset_lock_all(sdev->ddev); |
51c13278 | 309 | shmob_drm_crtc_resume(&sdev->crtc); |
b13f5980 | 310 | drm_modeset_unlock_all(sdev->ddev); |
51c13278 LP |
311 | |
312 | drm_kms_helper_poll_enable(sdev->ddev); | |
313 | return 0; | |
314 | } | |
315 | #endif | |
316 | ||
317 | static const struct dev_pm_ops shmob_drm_pm_ops = { | |
318 | SET_SYSTEM_SLEEP_PM_OPS(shmob_drm_pm_suspend, shmob_drm_pm_resume) | |
319 | }; | |
320 | ||
321 | /* ----------------------------------------------------------------------------- | |
322 | * Platform driver | |
323 | */ | |
324 | ||
56550d94 | 325 | static int shmob_drm_probe(struct platform_device *pdev) |
51c13278 LP |
326 | { |
327 | return drm_platform_init(&shmob_drm_driver, pdev); | |
328 | } | |
329 | ||
56550d94 | 330 | static int shmob_drm_remove(struct platform_device *pdev) |
51c13278 | 331 | { |
405bea7a DV |
332 | struct shmob_drm_device *sdev = platform_get_drvdata(pdev); |
333 | ||
334 | drm_put_dev(sdev->ddev); | |
51c13278 LP |
335 | |
336 | return 0; | |
337 | } | |
338 | ||
339 | static struct platform_driver shmob_drm_platform_driver = { | |
340 | .probe = shmob_drm_probe, | |
56550d94 | 341 | .remove = shmob_drm_remove, |
51c13278 | 342 | .driver = { |
51c13278 LP |
343 | .name = "shmob-drm", |
344 | .pm = &shmob_drm_pm_ops, | |
345 | }, | |
346 | }; | |
347 | ||
348 | module_platform_driver(shmob_drm_platform_driver); | |
349 | ||
350 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); | |
351 | MODULE_DESCRIPTION("Renesas SH Mobile DRM Driver"); | |
352 | MODULE_LICENSE("GPL"); |