1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2014-2015 Broadcom |
4 | * Copyright (C) 2013 Red Hat |
5 | */ |
6 | |
7 | /** |
8 | * DOC: Broadcom VC4 Graphics Driver |
9 | * |
10 | * The Broadcom VideoCore 4 (present in the Raspberry Pi) contains a |
11 | * OpenGL ES 2.0-compatible 3D engine called V3D, and a highly |
12 | * configurable display output pipeline that supports HDMI, DSI, DPI, |
13 | * and Composite TV output. |
14 | * |
15 | * The 3D engine also has an interface for submitting arbitrary |
16 | * compute shader-style jobs using the same shader processor as is |
17 | * used for vertex and fragment shaders in GLES 2.0. However, given |
18 | * that the hardware isn't able to expose any standard interfaces like |
19 | * OpenGL compute shaders or OpenCL, it isn't supported by this |
20 | * driver. |
21 | */ |
22 | |
23 | #include <linux/clk.h> |
24 | #include <linux/component.h> |
25 | #include <linux/device.h> |
26 | #include <linux/dma-mapping.h> |
27 | #include <linux/io.h> |
28 | #include <linux/module.h> |
29 | #include <linux/of_device.h> |
30 | #include <linux/platform_device.h> |
31 | #include <linux/pm_runtime.h> |
32 | |
33 | #include <drm/drm_aperture.h> |
34 | #include <drm/drm_atomic_helper.h> |
35 | #include <drm/drm_drv.h> |
36 | #include <drm/drm_fbdev_dma.h> |
37 | #include <drm/drm_vblank.h> |
38 | |
39 | #include <soc/bcm2835/raspberrypi-firmware.h> |
40 | |
41 | #include "uapi/drm/vc4_drm.h" |
42 | |
43 | #include "vc4_drv.h" |
44 | #include "vc4_regs.h" |
45 | |
46 | #define DRIVER_NAME "vc4" |
47 | #define DRIVER_DESC "Broadcom VC4 graphics" |
48 | #define DRIVER_DATE "20140616" |
49 | #define DRIVER_MAJOR 0 |
50 | #define DRIVER_MINOR 0 |
51 | #define DRIVER_PATCHLEVEL 0 |
52 | |
53 | /* Helper function for mapping the regs on a platform device. */ |
54 | void __iomem *vc4_ioremap_regs(struct platform_device *pdev, int index) |
55 | { |
56 | void __iomem *map; |
57 | |
58 | map = devm_platform_ioremap_resource(pdev, index); |
59 | if (IS_ERR(ptr: map)) |
60 | return map; |
61 | |
62 | return map; |
63 | } |
64 | |
65 | int vc4_dumb_fixup_args(struct drm_mode_create_dumb *args) |
66 | { |
67 | int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); |
68 | |
69 | if (args->pitch < min_pitch) |
70 | args->pitch = min_pitch; |
71 | |
72 | if (args->size < args->pitch * args->height) |
73 | args->size = args->pitch * args->height; |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int vc5_dumb_create(struct drm_file *file_priv, |
79 | struct drm_device *dev, |
80 | struct drm_mode_create_dumb *args) |
81 | { |
82 | int ret; |
83 | |
84 | ret = vc4_dumb_fixup_args(args); |
85 | if (ret) |
86 | return ret; |
87 | |
88 | return drm_gem_dma_dumb_create_internal(file_priv, drm: dev, args); |
89 | } |
90 | |
91 | static int vc4_get_param_ioctl(struct drm_device *dev, void *data, |
92 | struct drm_file *file_priv) |
93 | { |
94 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
95 | struct drm_vc4_get_param *args = data; |
96 | int ret; |
97 | |
98 | if (args->pad != 0) |
99 | return -EINVAL; |
100 | |
101 | if (WARN_ON_ONCE(vc4->is_vc5)) |
102 | return -ENODEV; |
103 | |
104 | if (!vc4->v3d) |
105 | return -ENODEV; |
106 | |
107 | switch (args->param) { |
108 | case DRM_VC4_PARAM_V3D_IDENT0: |
109 | ret = vc4_v3d_pm_get(vc4); |
110 | if (ret) |
111 | return ret; |
112 | args->value = V3D_READ(V3D_IDENT0); |
113 | vc4_v3d_pm_put(vc4); |
114 | break; |
115 | case DRM_VC4_PARAM_V3D_IDENT1: |
116 | ret = vc4_v3d_pm_get(vc4); |
117 | if (ret) |
118 | return ret; |
119 | args->value = V3D_READ(V3D_IDENT1); |
120 | vc4_v3d_pm_put(vc4); |
121 | break; |
122 | case DRM_VC4_PARAM_V3D_IDENT2: |
123 | ret = vc4_v3d_pm_get(vc4); |
124 | if (ret) |
125 | return ret; |
126 | args->value = V3D_READ(V3D_IDENT2); |
127 | vc4_v3d_pm_put(vc4); |
128 | break; |
129 | case DRM_VC4_PARAM_SUPPORTS_BRANCHES: |
130 | case DRM_VC4_PARAM_SUPPORTS_ETC1: |
131 | case DRM_VC4_PARAM_SUPPORTS_THREADED_FS: |
132 | case DRM_VC4_PARAM_SUPPORTS_FIXED_RCL_ORDER: |
133 | case DRM_VC4_PARAM_SUPPORTS_MADVISE: |
134 | case DRM_VC4_PARAM_SUPPORTS_PERFMON: |
135 | args->value = true; |
136 | break; |
137 | default: |
138 | DRM_DEBUG("Unknown parameter %d\n" , args->param); |
139 | return -EINVAL; |
140 | } |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static int vc4_open(struct drm_device *dev, struct drm_file *file) |
146 | { |
147 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
148 | struct vc4_file *vc4file; |
149 | |
150 | if (WARN_ON_ONCE(vc4->is_vc5)) |
151 | return -ENODEV; |
152 | |
153 | vc4file = kzalloc(size: sizeof(*vc4file), GFP_KERNEL); |
154 | if (!vc4file) |
155 | return -ENOMEM; |
156 | vc4file->dev = vc4; |
157 | |
158 | vc4_perfmon_open_file(vc4file); |
159 | file->driver_priv = vc4file; |
160 | return 0; |
161 | } |
162 | |
163 | static void vc4_close(struct drm_device *dev, struct drm_file *file) |
164 | { |
165 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
166 | struct vc4_file *vc4file = file->driver_priv; |
167 | |
168 | if (WARN_ON_ONCE(vc4->is_vc5)) |
169 | return; |
170 | |
171 | if (vc4file->bin_bo_used) |
172 | vc4_v3d_bin_bo_put(vc4); |
173 | |
174 | vc4_perfmon_close_file(vc4file); |
175 | kfree(objp: vc4file); |
176 | } |
177 | |
178 | DEFINE_DRM_GEM_FOPS(vc4_drm_fops); |
179 | |
180 | static const struct drm_ioctl_desc vc4_drm_ioctls[] = { |
181 | DRM_IOCTL_DEF_DRV(VC4_SUBMIT_CL, vc4_submit_cl_ioctl, DRM_RENDER_ALLOW), |
182 | DRM_IOCTL_DEF_DRV(VC4_WAIT_SEQNO, vc4_wait_seqno_ioctl, DRM_RENDER_ALLOW), |
183 | DRM_IOCTL_DEF_DRV(VC4_WAIT_BO, vc4_wait_bo_ioctl, DRM_RENDER_ALLOW), |
184 | DRM_IOCTL_DEF_DRV(VC4_CREATE_BO, vc4_create_bo_ioctl, DRM_RENDER_ALLOW), |
185 | DRM_IOCTL_DEF_DRV(VC4_MMAP_BO, vc4_mmap_bo_ioctl, DRM_RENDER_ALLOW), |
186 | DRM_IOCTL_DEF_DRV(VC4_CREATE_SHADER_BO, vc4_create_shader_bo_ioctl, DRM_RENDER_ALLOW), |
187 | DRM_IOCTL_DEF_DRV(VC4_GET_HANG_STATE, vc4_get_hang_state_ioctl, |
188 | DRM_ROOT_ONLY), |
189 | DRM_IOCTL_DEF_DRV(VC4_GET_PARAM, vc4_get_param_ioctl, DRM_RENDER_ALLOW), |
190 | DRM_IOCTL_DEF_DRV(VC4_SET_TILING, vc4_set_tiling_ioctl, DRM_RENDER_ALLOW), |
191 | DRM_IOCTL_DEF_DRV(VC4_GET_TILING, vc4_get_tiling_ioctl, DRM_RENDER_ALLOW), |
192 | DRM_IOCTL_DEF_DRV(VC4_LABEL_BO, vc4_label_bo_ioctl, DRM_RENDER_ALLOW), |
193 | DRM_IOCTL_DEF_DRV(VC4_GEM_MADVISE, vc4_gem_madvise_ioctl, DRM_RENDER_ALLOW), |
194 | DRM_IOCTL_DEF_DRV(VC4_PERFMON_CREATE, vc4_perfmon_create_ioctl, DRM_RENDER_ALLOW), |
195 | DRM_IOCTL_DEF_DRV(VC4_PERFMON_DESTROY, vc4_perfmon_destroy_ioctl, DRM_RENDER_ALLOW), |
196 | DRM_IOCTL_DEF_DRV(VC4_PERFMON_GET_VALUES, vc4_perfmon_get_values_ioctl, DRM_RENDER_ALLOW), |
197 | }; |
198 | |
199 | const struct drm_driver vc4_drm_driver = { |
200 | .driver_features = (DRIVER_MODESET | |
201 | DRIVER_ATOMIC | |
202 | DRIVER_GEM | |
203 | DRIVER_RENDER | |
204 | DRIVER_SYNCOBJ), |
205 | .open = vc4_open, |
206 | .postclose = vc4_close, |
207 | |
208 | #if defined(CONFIG_DEBUG_FS) |
209 | .debugfs_init = vc4_debugfs_init, |
210 | #endif |
211 | |
212 | .gem_create_object = vc4_create_object, |
213 | |
214 | DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_bo_dumb_create), |
215 | |
216 | .ioctls = vc4_drm_ioctls, |
217 | .num_ioctls = ARRAY_SIZE(vc4_drm_ioctls), |
218 | .fops = &vc4_drm_fops, |
219 | |
220 | .name = DRIVER_NAME, |
221 | .desc = DRIVER_DESC, |
222 | .date = DRIVER_DATE, |
223 | .major = DRIVER_MAJOR, |
224 | .minor = DRIVER_MINOR, |
225 | .patchlevel = DRIVER_PATCHLEVEL, |
226 | }; |
227 | |
228 | const struct drm_driver vc5_drm_driver = { |
229 | .driver_features = (DRIVER_MODESET | |
230 | DRIVER_ATOMIC | |
231 | DRIVER_GEM), |
232 | |
233 | #if defined(CONFIG_DEBUG_FS) |
234 | .debugfs_init = vc4_debugfs_init, |
235 | #endif |
236 | |
237 | DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc5_dumb_create), |
238 | |
239 | .fops = &vc4_drm_fops, |
240 | |
241 | .name = DRIVER_NAME, |
242 | .desc = DRIVER_DESC, |
243 | .date = DRIVER_DATE, |
244 | .major = DRIVER_MAJOR, |
245 | .minor = DRIVER_MINOR, |
246 | .patchlevel = DRIVER_PATCHLEVEL, |
247 | }; |
248 | |
249 | static void vc4_match_add_drivers(struct device *dev, |
250 | struct component_match **match, |
251 | struct platform_driver *const *drivers, |
252 | int count) |
253 | { |
254 | int i; |
255 | |
256 | for (i = 0; i < count; i++) { |
257 | struct device_driver *drv = &drivers[i]->driver; |
258 | struct device *p = NULL, *d; |
259 | |
260 | while ((d = platform_find_device_by_driver(start: p, drv))) { |
261 | put_device(dev: p); |
262 | component_match_add(parent: dev, matchptr: match, compare: component_compare_dev, compare_data: d); |
263 | p = d; |
264 | } |
265 | put_device(dev: p); |
266 | } |
267 | } |
268 | |
269 | static void vc4_component_unbind_all(void *ptr) |
270 | { |
271 | struct vc4_dev *vc4 = ptr; |
272 | |
273 | component_unbind_all(parent: vc4->dev, data: &vc4->base); |
274 | } |
275 | |
276 | static const struct of_device_id vc4_dma_range_matches[] = { |
277 | { .compatible = "brcm,bcm2711-hvs" }, |
278 | { .compatible = "brcm,bcm2835-hvs" }, |
279 | { .compatible = "brcm,bcm2835-v3d" }, |
280 | { .compatible = "brcm,cygnus-v3d" }, |
281 | { .compatible = "brcm,vc4-v3d" }, |
282 | {} |
283 | }; |
284 | |
285 | static int vc4_drm_bind(struct device *dev) |
286 | { |
287 | struct platform_device *pdev = to_platform_device(dev); |
288 | const struct drm_driver *driver; |
289 | struct rpi_firmware *firmware = NULL; |
290 | struct drm_device *drm; |
291 | struct vc4_dev *vc4; |
292 | struct device_node *node; |
293 | struct drm_crtc *crtc; |
294 | bool is_vc5; |
295 | int ret = 0; |
296 | |
297 | dev->coherent_dma_mask = DMA_BIT_MASK(32); |
298 | |
299 | is_vc5 = of_device_is_compatible(device: dev->of_node, "brcm,bcm2711-vc5" ); |
300 | if (is_vc5) |
301 | driver = &vc5_drm_driver; |
302 | else |
303 | driver = &vc4_drm_driver; |
304 | |
305 | node = of_find_matching_node_and_match(NULL, matches: vc4_dma_range_matches, |
306 | NULL); |
307 | if (node) { |
308 | ret = of_dma_configure(dev, np: node, force_dma: true); |
309 | of_node_put(node); |
310 | |
311 | if (ret) |
312 | return ret; |
313 | } |
314 | |
315 | vc4 = devm_drm_dev_alloc(dev, driver, struct vc4_dev, base); |
316 | if (IS_ERR(ptr: vc4)) |
317 | return PTR_ERR(ptr: vc4); |
318 | vc4->is_vc5 = is_vc5; |
319 | vc4->dev = dev; |
320 | |
321 | drm = &vc4->base; |
322 | platform_set_drvdata(pdev, data: drm); |
323 | |
324 | if (!is_vc5) { |
325 | ret = drmm_mutex_init(drm, &vc4->bin_bo_lock); |
326 | if (ret) |
327 | goto err; |
328 | |
329 | ret = vc4_bo_cache_init(dev: drm); |
330 | if (ret) |
331 | goto err; |
332 | } |
333 | |
334 | ret = drmm_mode_config_init(dev: drm); |
335 | if (ret) |
336 | goto err; |
337 | |
338 | if (!is_vc5) { |
339 | ret = vc4_gem_init(dev: drm); |
340 | if (ret) |
341 | goto err; |
342 | } |
343 | |
344 | node = of_find_compatible_node(NULL, NULL, compat: "raspberrypi,bcm2835-firmware" ); |
345 | if (node) { |
346 | firmware = rpi_firmware_get(firmware_node: node); |
347 | of_node_put(node); |
348 | |
349 | if (!firmware) { |
350 | ret = -EPROBE_DEFER; |
351 | goto err; |
352 | } |
353 | } |
354 | |
355 | ret = drm_aperture_remove_framebuffers(req_driver: driver); |
356 | if (ret) |
357 | goto err; |
358 | |
359 | if (firmware) { |
360 | ret = rpi_firmware_property(fw: firmware, |
361 | tag: RPI_FIRMWARE_NOTIFY_DISPLAY_DONE, |
362 | NULL, len: 0); |
363 | if (ret) |
364 | drm_warn(drm, "Couldn't stop firmware display driver: %d\n" , ret); |
365 | |
366 | rpi_firmware_put(fw: firmware); |
367 | } |
368 | |
369 | ret = component_bind_all(parent: dev, data: drm); |
370 | if (ret) |
371 | goto err; |
372 | |
373 | ret = devm_add_action_or_reset(dev, vc4_component_unbind_all, vc4); |
374 | if (ret) |
375 | goto err; |
376 | |
377 | ret = vc4_plane_create_additional_planes(dev: drm); |
378 | if (ret) |
379 | goto err; |
380 | |
381 | ret = vc4_kms_load(dev: drm); |
382 | if (ret < 0) |
383 | goto err; |
384 | |
385 | drm_for_each_crtc(crtc, drm) |
386 | vc4_crtc_disable_at_boot(crtc); |
387 | |
388 | ret = drm_dev_register(dev: drm, flags: 0); |
389 | if (ret < 0) |
390 | goto err; |
391 | |
392 | drm_fbdev_dma_setup(dev: drm, preferred_bpp: 16); |
393 | |
394 | return 0; |
395 | |
396 | err: |
397 | platform_set_drvdata(pdev, NULL); |
398 | return ret; |
399 | } |
400 | |
401 | static void vc4_drm_unbind(struct device *dev) |
402 | { |
403 | struct drm_device *drm = dev_get_drvdata(dev); |
404 | |
405 | drm_dev_unplug(dev: drm); |
406 | drm_atomic_helper_shutdown(dev: drm); |
407 | dev_set_drvdata(dev, NULL); |
408 | } |
409 | |
410 | static const struct component_master_ops vc4_drm_ops = { |
411 | .bind = vc4_drm_bind, |
412 | .unbind = vc4_drm_unbind, |
413 | }; |
414 | |
415 | /* |
416 | * This list determines the binding order of our components, and we have |
417 | * a few constraints: |
418 | * - The TXP driver needs to be bound before the PixelValves (CRTC) |
419 | * but after the HVS to set the possible_crtc field properly |
420 | * - The HDMI driver needs to be bound after the HVS so that we can |
421 | * lookup the HVS maximum core clock rate and figure out if we |
422 | * support 4kp60 or not. |
423 | */ |
424 | static struct platform_driver *const component_drivers[] = { |
425 | &vc4_hvs_driver, |
426 | &vc4_hdmi_driver, |
427 | &vc4_vec_driver, |
428 | &vc4_dpi_driver, |
429 | &vc4_dsi_driver, |
430 | &vc4_txp_driver, |
431 | &vc4_crtc_driver, |
432 | &vc4_v3d_driver, |
433 | }; |
434 | |
435 | static int vc4_platform_drm_probe(struct platform_device *pdev) |
436 | { |
437 | struct component_match *match = NULL; |
438 | struct device *dev = &pdev->dev; |
439 | |
440 | vc4_match_add_drivers(dev, match: &match, |
441 | drivers: component_drivers, ARRAY_SIZE(component_drivers)); |
442 | |
443 | return component_master_add_with_match(dev, &vc4_drm_ops, match); |
444 | } |
445 | |
446 | static void vc4_platform_drm_remove(struct platform_device *pdev) |
447 | { |
448 | component_master_del(&pdev->dev, &vc4_drm_ops); |
449 | } |
450 | |
451 | static void vc4_platform_drm_shutdown(struct platform_device *pdev) |
452 | { |
453 | drm_atomic_helper_shutdown(dev: platform_get_drvdata(pdev)); |
454 | } |
455 | |
456 | static const struct of_device_id vc4_of_match[] = { |
457 | { .compatible = "brcm,bcm2711-vc5" , }, |
458 | { .compatible = "brcm,bcm2835-vc4" , }, |
459 | { .compatible = "brcm,cygnus-vc4" , }, |
460 | {}, |
461 | }; |
462 | MODULE_DEVICE_TABLE(of, vc4_of_match); |
463 | |
464 | static struct platform_driver vc4_platform_driver = { |
465 | .probe = vc4_platform_drm_probe, |
466 | .remove_new = vc4_platform_drm_remove, |
467 | .shutdown = vc4_platform_drm_shutdown, |
468 | .driver = { |
469 | .name = "vc4-drm" , |
470 | .of_match_table = vc4_of_match, |
471 | }, |
472 | }; |
473 | |
474 | static int __init vc4_drm_register(void) |
475 | { |
476 | int ret; |
477 | |
478 | if (drm_firmware_drivers_only()) |
479 | return -ENODEV; |
480 | |
481 | ret = platform_register_drivers(component_drivers, |
482 | ARRAY_SIZE(component_drivers)); |
483 | if (ret) |
484 | return ret; |
485 | |
486 | ret = platform_driver_register(&vc4_platform_driver); |
487 | if (ret) |
488 | platform_unregister_drivers(drivers: component_drivers, |
489 | ARRAY_SIZE(component_drivers)); |
490 | |
491 | return ret; |
492 | } |
493 | |
494 | static void __exit vc4_drm_unregister(void) |
495 | { |
496 | platform_unregister_drivers(drivers: component_drivers, |
497 | ARRAY_SIZE(component_drivers)); |
498 | platform_driver_unregister(&vc4_platform_driver); |
499 | } |
500 | |
501 | module_init(vc4_drm_register); |
502 | module_exit(vc4_drm_unregister); |
503 | |
504 | MODULE_ALIAS("platform:vc4-drm" ); |
505 | MODULE_SOFTDEP("pre: snd-soc-hdmi-codec" ); |
506 | MODULE_DESCRIPTION("Broadcom VC4 DRM Driver" ); |
507 | MODULE_AUTHOR("Eric Anholt <eric@anholt.net>" ); |
508 | MODULE_LICENSE("GPL v2" ); |
509 | |