Commit | Line | Data |
---|---|---|
96f60e37 RK |
1 | /* |
2 | * Copyright (C) 2012 Russell King | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | */ | |
8 | #include <linux/clk.h> | |
0101fd00 | 9 | #include <linux/component.h> |
96f60e37 | 10 | #include <linux/module.h> |
0101fd00 | 11 | #include <linux/of_graph.h> |
96f60e37 RK |
12 | #include <drm/drmP.h> |
13 | #include <drm/drm_crtc_helper.h> | |
14 | #include "armada_crtc.h" | |
15 | #include "armada_drm.h" | |
16 | #include "armada_gem.h" | |
17 | #include "armada_hw.h" | |
18 | #include <drm/armada_drm.h> | |
19 | #include "armada_ioctlP.h" | |
20 | ||
21 | static void armada_drm_unref_work(struct work_struct *work) | |
22 | { | |
23 | struct armada_private *priv = | |
24 | container_of(work, struct armada_private, fb_unref_work); | |
25 | struct drm_framebuffer *fb; | |
26 | ||
27 | while (kfifo_get(&priv->fb_unref, &fb)) | |
28 | drm_framebuffer_unreference(fb); | |
29 | } | |
30 | ||
31 | /* Must be called with dev->event_lock held */ | |
32 | void __armada_drm_queue_unref_work(struct drm_device *dev, | |
33 | struct drm_framebuffer *fb) | |
34 | { | |
35 | struct armada_private *priv = dev->dev_private; | |
36 | ||
d13c46c6 | 37 | WARN_ON(!kfifo_put(&priv->fb_unref, fb)); |
96f60e37 RK |
38 | schedule_work(&priv->fb_unref_work); |
39 | } | |
40 | ||
41 | void armada_drm_queue_unref_work(struct drm_device *dev, | |
42 | struct drm_framebuffer *fb) | |
43 | { | |
44 | unsigned long flags; | |
45 | ||
46 | spin_lock_irqsave(&dev->event_lock, flags); | |
47 | __armada_drm_queue_unref_work(dev, fb); | |
48 | spin_unlock_irqrestore(&dev->event_lock, flags); | |
49 | } | |
50 | ||
51 | static int armada_drm_load(struct drm_device *dev, unsigned long flags) | |
52 | { | |
96f60e37 | 53 | struct armada_private *priv; |
96f60e37 | 54 | struct resource *mem = NULL; |
0fb2970b | 55 | int ret, n; |
96f60e37 | 56 | |
0fb2970b | 57 | for (n = 0; ; n++) { |
96f60e37 RK |
58 | struct resource *r = platform_get_resource(dev->platformdev, |
59 | IORESOURCE_MEM, n); | |
60 | if (!r) | |
61 | break; | |
62 | ||
63 | /* Resources above 64K are graphics memory */ | |
64 | if (resource_size(r) > SZ_64K) | |
65 | mem = r; | |
96f60e37 RK |
66 | else |
67 | return -EINVAL; | |
68 | } | |
69 | ||
d8c96083 | 70 | if (!mem) |
96f60e37 RK |
71 | return -ENXIO; |
72 | ||
73 | if (!devm_request_mem_region(dev->dev, mem->start, | |
74 | resource_size(mem), "armada-drm")) | |
75 | return -EBUSY; | |
76 | ||
77 | priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); | |
78 | if (!priv) { | |
79 | DRM_ERROR("failed to allocate private\n"); | |
80 | return -ENOMEM; | |
81 | } | |
82 | ||
50fb3c3b | 83 | platform_set_drvdata(dev->platformdev, dev); |
96f60e37 RK |
84 | dev->dev_private = priv; |
85 | ||
96f60e37 RK |
86 | INIT_WORK(&priv->fb_unref_work, armada_drm_unref_work); |
87 | INIT_KFIFO(priv->fb_unref); | |
88 | ||
89 | /* Mode setting support */ | |
90 | drm_mode_config_init(dev); | |
91 | dev->mode_config.min_width = 320; | |
92 | dev->mode_config.min_height = 200; | |
93 | ||
94 | /* | |
95 | * With vscale enabled, the maximum width is 1920 due to the | |
96 | * 1920 by 3 lines RAM | |
97 | */ | |
98 | dev->mode_config.max_width = 1920; | |
99 | dev->mode_config.max_height = 2048; | |
100 | ||
101 | dev->mode_config.preferred_depth = 24; | |
102 | dev->mode_config.funcs = &armada_drm_mode_config_funcs; | |
103 | drm_mm_init(&priv->linear, mem->start, resource_size(mem)); | |
104 | ||
0fb2970b RK |
105 | ret = component_bind_all(dev->dev, dev); |
106 | if (ret) | |
107 | goto err_kms; | |
585b691e | 108 | |
d0165407 | 109 | ret = drm_vblank_init(dev, dev->mode_config.num_crtc); |
96f60e37 | 110 | if (ret) |
0101fd00 | 111 | goto err_comp; |
96f60e37 | 112 | |
0544e38d | 113 | dev->irq_enabled = true; |
96f60e37 RK |
114 | dev->vblank_disable_allowed = 1; |
115 | ||
116 | ret = armada_fbdev_init(dev); | |
117 | if (ret) | |
0101fd00 | 118 | goto err_comp; |
96f60e37 RK |
119 | |
120 | drm_kms_helper_poll_init(dev); | |
121 | ||
122 | return 0; | |
123 | ||
0101fd00 | 124 | err_comp: |
0fb2970b | 125 | component_unbind_all(dev->dev, dev); |
96f60e37 RK |
126 | err_kms: |
127 | drm_mode_config_cleanup(dev); | |
128 | drm_mm_takedown(&priv->linear); | |
129 | flush_work(&priv->fb_unref_work); | |
130 | ||
131 | return ret; | |
132 | } | |
133 | ||
134 | static int armada_drm_unload(struct drm_device *dev) | |
135 | { | |
136 | struct armada_private *priv = dev->dev_private; | |
137 | ||
138 | drm_kms_helper_poll_fini(dev); | |
139 | armada_fbdev_fini(dev); | |
0101fd00 | 140 | |
0fb2970b | 141 | component_unbind_all(dev->dev, dev); |
0101fd00 | 142 | |
96f60e37 RK |
143 | drm_mode_config_cleanup(dev); |
144 | drm_mm_takedown(&priv->linear); | |
145 | flush_work(&priv->fb_unref_work); | |
146 | dev->dev_private = NULL; | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
96f60e37 RK |
151 | /* These are called under the vbl_lock. */ |
152 | static int armada_drm_enable_vblank(struct drm_device *dev, int crtc) | |
153 | { | |
154 | struct armada_private *priv = dev->dev_private; | |
155 | armada_drm_crtc_enable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA); | |
156 | return 0; | |
157 | } | |
158 | ||
159 | static void armada_drm_disable_vblank(struct drm_device *dev, int crtc) | |
160 | { | |
161 | struct armada_private *priv = dev->dev_private; | |
162 | armada_drm_crtc_disable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA); | |
163 | } | |
164 | ||
96f60e37 RK |
165 | static struct drm_ioctl_desc armada_ioctls[] = { |
166 | DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, | |
167 | DRM_UNLOCKED), | |
168 | DRM_IOCTL_DEF_DRV(ARMADA_GEM_MMAP, armada_gem_mmap_ioctl, | |
169 | DRM_UNLOCKED), | |
170 | DRM_IOCTL_DEF_DRV(ARMADA_GEM_PWRITE, armada_gem_pwrite_ioctl, | |
171 | DRM_UNLOCKED), | |
172 | }; | |
173 | ||
2f5ae490 RK |
174 | static void armada_drm_lastclose(struct drm_device *dev) |
175 | { | |
176 | armada_fbdev_lastclose(dev); | |
177 | } | |
178 | ||
96f60e37 RK |
179 | static const struct file_operations armada_drm_fops = { |
180 | .owner = THIS_MODULE, | |
181 | .llseek = no_llseek, | |
182 | .read = drm_read, | |
183 | .poll = drm_poll, | |
184 | .unlocked_ioctl = drm_ioctl, | |
185 | .mmap = drm_gem_mmap, | |
186 | .open = drm_open, | |
187 | .release = drm_release, | |
188 | }; | |
189 | ||
190 | static struct drm_driver armada_drm_driver = { | |
191 | .load = armada_drm_load, | |
192 | .open = NULL, | |
193 | .preclose = NULL, | |
194 | .postclose = NULL, | |
2f5ae490 | 195 | .lastclose = armada_drm_lastclose, |
96f60e37 | 196 | .unload = armada_drm_unload, |
915b4d11 | 197 | .set_busid = drm_platform_set_busid, |
96f60e37 RK |
198 | .get_vblank_counter = drm_vblank_count, |
199 | .enable_vblank = armada_drm_enable_vblank, | |
200 | .disable_vblank = armada_drm_disable_vblank, | |
96f60e37 RK |
201 | #ifdef CONFIG_DEBUG_FS |
202 | .debugfs_init = armada_drm_debugfs_init, | |
203 | .debugfs_cleanup = armada_drm_debugfs_cleanup, | |
204 | #endif | |
96f60e37 RK |
205 | .gem_free_object = armada_gem_free_object, |
206 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | |
207 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
208 | .gem_prime_export = armada_gem_prime_export, | |
209 | .gem_prime_import = armada_gem_prime_import, | |
210 | .dumb_create = armada_gem_dumb_create, | |
211 | .dumb_map_offset = armada_gem_dumb_map_offset, | |
212 | .dumb_destroy = armada_gem_dumb_destroy, | |
213 | .gem_vm_ops = &armada_gem_vm_ops, | |
214 | .major = 1, | |
215 | .minor = 0, | |
216 | .name = "armada-drm", | |
217 | .desc = "Armada SoC DRM", | |
218 | .date = "20120730", | |
219 | .driver_features = DRIVER_GEM | DRIVER_MODESET | | |
0544e38d | 220 | DRIVER_HAVE_IRQ | DRIVER_PRIME, |
96f60e37 RK |
221 | .ioctls = armada_ioctls, |
222 | .fops = &armada_drm_fops, | |
223 | }; | |
224 | ||
0101fd00 RK |
225 | static int armada_drm_bind(struct device *dev) |
226 | { | |
227 | return drm_platform_init(&armada_drm_driver, to_platform_device(dev)); | |
228 | } | |
229 | ||
230 | static void armada_drm_unbind(struct device *dev) | |
231 | { | |
232 | drm_put_dev(dev_get_drvdata(dev)); | |
233 | } | |
234 | ||
235 | static int compare_of(struct device *dev, void *data) | |
236 | { | |
237 | return dev->of_node == data; | |
238 | } | |
239 | ||
240 | static int compare_dev_name(struct device *dev, void *data) | |
241 | { | |
242 | const char *name = data; | |
243 | return !strcmp(dev_name(dev), name); | |
244 | } | |
245 | ||
246 | static void armada_add_endpoints(struct device *dev, | |
247 | struct component_match **match, struct device_node *port) | |
248 | { | |
249 | struct device_node *ep, *remote; | |
250 | ||
251 | for_each_child_of_node(port, ep) { | |
252 | remote = of_graph_get_remote_port_parent(ep); | |
253 | if (!remote || !of_device_is_available(remote)) { | |
254 | of_node_put(remote); | |
255 | continue; | |
256 | } else if (!of_device_is_available(remote->parent)) { | |
257 | dev_warn(dev, "parent device of %s is not available\n", | |
258 | remote->full_name); | |
259 | of_node_put(remote); | |
260 | continue; | |
261 | } | |
262 | ||
263 | component_match_add(dev, match, compare_of, remote); | |
264 | of_node_put(remote); | |
265 | } | |
266 | } | |
267 | ||
268 | static int armada_drm_find_components(struct device *dev, | |
269 | struct component_match **match) | |
270 | { | |
271 | struct device_node *port; | |
272 | int i; | |
273 | ||
274 | if (dev->of_node) { | |
275 | struct device_node *np = dev->of_node; | |
276 | ||
277 | for (i = 0; ; i++) { | |
278 | port = of_parse_phandle(np, "ports", i); | |
279 | if (!port) | |
280 | break; | |
281 | ||
282 | component_match_add(dev, match, compare_of, port); | |
283 | of_node_put(port); | |
284 | } | |
285 | ||
286 | if (i == 0) { | |
287 | dev_err(dev, "missing 'ports' property\n"); | |
288 | return -ENODEV; | |
289 | } | |
290 | ||
291 | for (i = 0; ; i++) { | |
292 | port = of_parse_phandle(np, "ports", i); | |
293 | if (!port) | |
294 | break; | |
295 | ||
296 | armada_add_endpoints(dev, match, port); | |
297 | of_node_put(port); | |
298 | } | |
299 | } else if (dev->platform_data) { | |
300 | char **devices = dev->platform_data; | |
301 | struct device *d; | |
302 | ||
303 | for (i = 0; devices[i]; i++) | |
304 | component_match_add(dev, match, compare_dev_name, | |
305 | devices[i]); | |
306 | ||
307 | if (i == 0) { | |
308 | dev_err(dev, "missing 'ports' property\n"); | |
309 | return -ENODEV; | |
310 | } | |
311 | ||
312 | for (i = 0; devices[i]; i++) { | |
313 | d = bus_find_device_by_name(&platform_bus_type, NULL, | |
314 | devices[i]); | |
315 | if (d && d->of_node) { | |
316 | for_each_child_of_node(d->of_node, port) | |
317 | armada_add_endpoints(dev, match, port); | |
318 | } | |
319 | put_device(d); | |
320 | } | |
321 | } | |
322 | ||
323 | return 0; | |
324 | } | |
325 | ||
326 | static const struct component_master_ops armada_master_ops = { | |
327 | .bind = armada_drm_bind, | |
328 | .unbind = armada_drm_unbind, | |
329 | }; | |
330 | ||
96f60e37 RK |
331 | static int armada_drm_probe(struct platform_device *pdev) |
332 | { | |
0fb2970b RK |
333 | struct component_match *match = NULL; |
334 | int ret; | |
335 | ||
336 | ret = armada_drm_find_components(&pdev->dev, &match); | |
337 | if (ret < 0) | |
338 | return ret; | |
339 | ||
340 | return component_master_add_with_match(&pdev->dev, &armada_master_ops, | |
341 | match); | |
96f60e37 RK |
342 | } |
343 | ||
344 | static int armada_drm_remove(struct platform_device *pdev) | |
345 | { | |
0fb2970b | 346 | component_master_del(&pdev->dev, &armada_master_ops); |
96f60e37 RK |
347 | return 0; |
348 | } | |
349 | ||
350 | static const struct platform_device_id armada_drm_platform_ids[] = { | |
351 | { | |
352 | .name = "armada-drm", | |
96f60e37 RK |
353 | }, { |
354 | .name = "armada-510-drm", | |
96f60e37 RK |
355 | }, |
356 | { }, | |
357 | }; | |
358 | MODULE_DEVICE_TABLE(platform, armada_drm_platform_ids); | |
359 | ||
360 | static struct platform_driver armada_drm_platform_driver = { | |
361 | .probe = armada_drm_probe, | |
362 | .remove = armada_drm_remove, | |
363 | .driver = { | |
364 | .name = "armada-drm", | |
96f60e37 RK |
365 | }, |
366 | .id_table = armada_drm_platform_ids, | |
367 | }; | |
368 | ||
369 | static int __init armada_drm_init(void) | |
370 | { | |
d8c96083 RK |
371 | int ret; |
372 | ||
f95aeb17 | 373 | armada_drm_driver.num_ioctls = ARRAY_SIZE(armada_ioctls); |
d8c96083 RK |
374 | |
375 | ret = platform_driver_register(&armada_lcd_platform_driver); | |
376 | if (ret) | |
377 | return ret; | |
378 | ret = platform_driver_register(&armada_drm_platform_driver); | |
379 | if (ret) | |
380 | platform_driver_unregister(&armada_lcd_platform_driver); | |
381 | return ret; | |
96f60e37 RK |
382 | } |
383 | module_init(armada_drm_init); | |
384 | ||
385 | static void __exit armada_drm_exit(void) | |
386 | { | |
387 | platform_driver_unregister(&armada_drm_platform_driver); | |
d8c96083 | 388 | platform_driver_unregister(&armada_lcd_platform_driver); |
96f60e37 RK |
389 | } |
390 | module_exit(armada_drm_exit); | |
391 | ||
392 | MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>"); | |
393 | MODULE_DESCRIPTION("Armada DRM Driver"); | |
394 | MODULE_LICENSE("GPL"); | |
395 | MODULE_ALIAS("platform:armada-drm"); |