1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd |
4 | * Author:Mark Yao <mark.yao@rock-chips.com> |
5 | * |
6 | * based on exynos_drm_drv.c |
7 | */ |
8 | |
9 | #include <linux/dma-mapping.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/of_platform.h> |
15 | #include <linux/component.h> |
16 | #include <linux/console.h> |
17 | #include <linux/iommu.h> |
18 | |
19 | #include <drm/drm_aperture.h> |
20 | #include <drm/drm_drv.h> |
21 | #include <drm/drm_fbdev_generic.h> |
22 | #include <drm/drm_gem_dma_helper.h> |
23 | #include <drm/drm_of.h> |
24 | #include <drm/drm_probe_helper.h> |
25 | #include <drm/drm_vblank.h> |
26 | |
27 | #if defined(CONFIG_ARM_DMA_USE_IOMMU) |
28 | #include <asm/dma-iommu.h> |
29 | #else |
30 | #define arm_iommu_detach_device(...) ({ }) |
31 | #define arm_iommu_release_mapping(...) ({ }) |
32 | #define to_dma_iommu_mapping(dev) NULL |
33 | #endif |
34 | |
35 | #include "rockchip_drm_drv.h" |
36 | #include "rockchip_drm_fb.h" |
37 | #include "rockchip_drm_gem.h" |
38 | |
39 | #define DRIVER_NAME "rockchip" |
40 | #define DRIVER_DESC "RockChip Soc DRM" |
41 | #define DRIVER_DATE "20140818" |
42 | #define DRIVER_MAJOR 1 |
43 | #define DRIVER_MINOR 0 |
44 | |
45 | static const struct drm_driver rockchip_drm_driver; |
46 | |
47 | /* |
48 | * Attach a (component) device to the shared drm dma mapping from master drm |
49 | * device. This is used by the VOPs to map GEM buffers to a common DMA |
50 | * mapping. |
51 | */ |
52 | int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, |
53 | struct device *dev) |
54 | { |
55 | struct rockchip_drm_private *private = drm_dev->dev_private; |
56 | int ret; |
57 | |
58 | if (!private->domain) |
59 | return 0; |
60 | |
61 | if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) { |
62 | struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev); |
63 | |
64 | if (mapping) { |
65 | arm_iommu_detach_device(dev); |
66 | arm_iommu_release_mapping(mapping); |
67 | } |
68 | } |
69 | |
70 | ret = iommu_attach_device(domain: private->domain, dev); |
71 | if (ret) { |
72 | DRM_DEV_ERROR(dev, "Failed to attach iommu device\n" ); |
73 | return ret; |
74 | } |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, |
80 | struct device *dev) |
81 | { |
82 | struct rockchip_drm_private *private = drm_dev->dev_private; |
83 | |
84 | if (!private->domain) |
85 | return; |
86 | |
87 | iommu_detach_device(domain: private->domain, dev); |
88 | } |
89 | |
90 | void rockchip_drm_dma_init_device(struct drm_device *drm_dev, |
91 | struct device *dev) |
92 | { |
93 | struct rockchip_drm_private *private = drm_dev->dev_private; |
94 | |
95 | if (!device_iommu_mapped(dev)) |
96 | private->iommu_dev = ERR_PTR(error: -ENODEV); |
97 | else if (!private->iommu_dev) |
98 | private->iommu_dev = dev; |
99 | } |
100 | |
101 | static int rockchip_drm_init_iommu(struct drm_device *drm_dev) |
102 | { |
103 | struct rockchip_drm_private *private = drm_dev->dev_private; |
104 | struct iommu_domain_geometry *geometry; |
105 | u64 start, end; |
106 | |
107 | if (IS_ERR_OR_NULL(ptr: private->iommu_dev)) |
108 | return 0; |
109 | |
110 | private->domain = iommu_domain_alloc(bus: private->iommu_dev->bus); |
111 | if (!private->domain) |
112 | return -ENOMEM; |
113 | |
114 | geometry = &private->domain->geometry; |
115 | start = geometry->aperture_start; |
116 | end = geometry->aperture_end; |
117 | |
118 | DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n" , |
119 | start, end); |
120 | drm_mm_init(mm: &private->mm, start, size: end - start + 1); |
121 | mutex_init(&private->mm_lock); |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static void rockchip_iommu_cleanup(struct drm_device *drm_dev) |
127 | { |
128 | struct rockchip_drm_private *private = drm_dev->dev_private; |
129 | |
130 | if (!private->domain) |
131 | return; |
132 | |
133 | drm_mm_takedown(mm: &private->mm); |
134 | iommu_domain_free(domain: private->domain); |
135 | } |
136 | |
137 | static int rockchip_drm_bind(struct device *dev) |
138 | { |
139 | struct drm_device *drm_dev; |
140 | struct rockchip_drm_private *private; |
141 | int ret; |
142 | |
143 | /* Remove existing drivers that may own the framebuffer memory. */ |
144 | ret = drm_aperture_remove_framebuffers(req_driver: &rockchip_drm_driver); |
145 | if (ret) { |
146 | DRM_DEV_ERROR(dev, |
147 | "Failed to remove existing framebuffers - %d.\n" , |
148 | ret); |
149 | return ret; |
150 | } |
151 | |
152 | drm_dev = drm_dev_alloc(driver: &rockchip_drm_driver, parent: dev); |
153 | if (IS_ERR(ptr: drm_dev)) |
154 | return PTR_ERR(ptr: drm_dev); |
155 | |
156 | dev_set_drvdata(dev, data: drm_dev); |
157 | |
158 | private = devm_kzalloc(dev: drm_dev->dev, size: sizeof(*private), GFP_KERNEL); |
159 | if (!private) { |
160 | ret = -ENOMEM; |
161 | goto err_free; |
162 | } |
163 | |
164 | drm_dev->dev_private = private; |
165 | |
166 | ret = drmm_mode_config_init(dev: drm_dev); |
167 | if (ret) |
168 | goto err_free; |
169 | |
170 | rockchip_drm_mode_config_init(dev: drm_dev); |
171 | |
172 | /* Try to bind all sub drivers. */ |
173 | ret = component_bind_all(parent: dev, data: drm_dev); |
174 | if (ret) |
175 | goto err_free; |
176 | |
177 | ret = rockchip_drm_init_iommu(drm_dev); |
178 | if (ret) |
179 | goto err_unbind_all; |
180 | |
181 | ret = drm_vblank_init(dev: drm_dev, num_crtcs: drm_dev->mode_config.num_crtc); |
182 | if (ret) |
183 | goto err_iommu_cleanup; |
184 | |
185 | drm_mode_config_reset(dev: drm_dev); |
186 | |
187 | /* init kms poll for handling hpd */ |
188 | drm_kms_helper_poll_init(dev: drm_dev); |
189 | |
190 | ret = drm_dev_register(dev: drm_dev, flags: 0); |
191 | if (ret) |
192 | goto err_kms_helper_poll_fini; |
193 | |
194 | drm_fbdev_generic_setup(dev: drm_dev, preferred_bpp: 0); |
195 | |
196 | return 0; |
197 | err_kms_helper_poll_fini: |
198 | drm_kms_helper_poll_fini(dev: drm_dev); |
199 | err_iommu_cleanup: |
200 | rockchip_iommu_cleanup(drm_dev); |
201 | err_unbind_all: |
202 | component_unbind_all(parent: dev, data: drm_dev); |
203 | err_free: |
204 | drm_dev_put(dev: drm_dev); |
205 | return ret; |
206 | } |
207 | |
208 | static void rockchip_drm_unbind(struct device *dev) |
209 | { |
210 | struct drm_device *drm_dev = dev_get_drvdata(dev); |
211 | |
212 | drm_dev_unregister(dev: drm_dev); |
213 | |
214 | drm_kms_helper_poll_fini(dev: drm_dev); |
215 | |
216 | drm_atomic_helper_shutdown(dev: drm_dev); |
217 | component_unbind_all(parent: dev, data: drm_dev); |
218 | rockchip_iommu_cleanup(drm_dev); |
219 | |
220 | drm_dev_put(dev: drm_dev); |
221 | } |
222 | |
223 | DEFINE_DRM_GEM_FOPS(rockchip_drm_driver_fops); |
224 | |
225 | static const struct drm_driver rockchip_drm_driver = { |
226 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, |
227 | .dumb_create = rockchip_gem_dumb_create, |
228 | .gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table, |
229 | .fops = &rockchip_drm_driver_fops, |
230 | .name = DRIVER_NAME, |
231 | .desc = DRIVER_DESC, |
232 | .date = DRIVER_DATE, |
233 | .major = DRIVER_MAJOR, |
234 | .minor = DRIVER_MINOR, |
235 | }; |
236 | |
237 | #ifdef CONFIG_PM_SLEEP |
238 | static int rockchip_drm_sys_suspend(struct device *dev) |
239 | { |
240 | struct drm_device *drm = dev_get_drvdata(dev); |
241 | |
242 | return drm_mode_config_helper_suspend(dev: drm); |
243 | } |
244 | |
245 | static int rockchip_drm_sys_resume(struct device *dev) |
246 | { |
247 | struct drm_device *drm = dev_get_drvdata(dev); |
248 | |
249 | return drm_mode_config_helper_resume(dev: drm); |
250 | } |
251 | #endif |
252 | |
253 | static const struct dev_pm_ops rockchip_drm_pm_ops = { |
254 | SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend, |
255 | rockchip_drm_sys_resume) |
256 | }; |
257 | |
258 | #define MAX_ROCKCHIP_SUB_DRIVERS 16 |
259 | static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS]; |
260 | static int num_rockchip_sub_drivers; |
261 | |
262 | /* |
263 | * Get the endpoint id of the remote endpoint of the given encoder. This |
264 | * information is used by the VOP2 driver to identify the encoder. |
265 | * |
266 | * @rkencoder: The encoder to get the remote endpoint id from |
267 | * @np: The encoder device node |
268 | * @port: The number of the port leading to the VOP2 |
269 | * @reg: The endpoint number leading to the VOP2 |
270 | */ |
271 | int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rkencoder, |
272 | struct device_node *np, int port, int reg) |
273 | { |
274 | struct of_endpoint ep; |
275 | struct device_node *en, *ren; |
276 | int ret; |
277 | |
278 | en = of_graph_get_endpoint_by_regs(parent: np, port_reg: port, reg); |
279 | if (!en) |
280 | return -ENOENT; |
281 | |
282 | ren = of_graph_get_remote_endpoint(node: en); |
283 | if (!ren) |
284 | return -ENOENT; |
285 | |
286 | ret = of_graph_parse_endpoint(node: ren, endpoint: &ep); |
287 | if (ret) |
288 | return ret; |
289 | |
290 | rkencoder->crtc_endpoint_id = ep.id; |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | /* |
296 | * Check if a vop endpoint is leading to a rockchip subdriver or bridge. |
297 | * Should be called from the component bind stage of the drivers |
298 | * to ensure that all subdrivers are probed. |
299 | * |
300 | * @ep: endpoint of a rockchip vop |
301 | * |
302 | * returns true if subdriver, false if external bridge and -ENODEV |
303 | * if remote port does not contain a device. |
304 | */ |
305 | int rockchip_drm_endpoint_is_subdriver(struct device_node *ep) |
306 | { |
307 | struct device_node *node = of_graph_get_remote_port_parent(node: ep); |
308 | struct platform_device *pdev; |
309 | struct device_driver *drv; |
310 | int i; |
311 | |
312 | if (!node) |
313 | return -ENODEV; |
314 | |
315 | /* status disabled will prevent creation of platform-devices */ |
316 | if (!of_device_is_available(device: node)) { |
317 | of_node_put(node); |
318 | return -ENODEV; |
319 | } |
320 | |
321 | pdev = of_find_device_by_node(np: node); |
322 | of_node_put(node); |
323 | |
324 | /* enabled non-platform-devices can immediately return here */ |
325 | if (!pdev) |
326 | return false; |
327 | |
328 | /* |
329 | * All rockchip subdrivers have probed at this point, so |
330 | * any device not having a driver now is an external bridge. |
331 | */ |
332 | drv = pdev->dev.driver; |
333 | if (!drv) { |
334 | platform_device_put(pdev); |
335 | return false; |
336 | } |
337 | |
338 | for (i = 0; i < num_rockchip_sub_drivers; i++) { |
339 | if (rockchip_sub_drivers[i] == to_platform_driver(drv)) { |
340 | platform_device_put(pdev); |
341 | return true; |
342 | } |
343 | } |
344 | |
345 | platform_device_put(pdev); |
346 | return false; |
347 | } |
348 | |
349 | static void rockchip_drm_match_remove(struct device *dev) |
350 | { |
351 | struct device_link *link; |
352 | |
353 | list_for_each_entry(link, &dev->links.consumers, s_node) |
354 | device_link_del(link); |
355 | } |
356 | |
357 | static struct component_match *rockchip_drm_match_add(struct device *dev) |
358 | { |
359 | struct component_match *match = NULL; |
360 | int i; |
361 | |
362 | for (i = 0; i < num_rockchip_sub_drivers; i++) { |
363 | struct platform_driver *drv = rockchip_sub_drivers[i]; |
364 | struct device *p = NULL, *d; |
365 | |
366 | do { |
367 | d = platform_find_device_by_driver(start: p, drv: &drv->driver); |
368 | put_device(dev: p); |
369 | p = d; |
370 | |
371 | if (!d) |
372 | break; |
373 | |
374 | device_link_add(consumer: dev, supplier: d, DL_FLAG_STATELESS); |
375 | component_match_add(parent: dev, matchptr: &match, compare: component_compare_dev, compare_data: d); |
376 | } while (true); |
377 | } |
378 | |
379 | if (IS_ERR(ptr: match)) |
380 | rockchip_drm_match_remove(dev); |
381 | |
382 | return match ?: ERR_PTR(error: -ENODEV); |
383 | } |
384 | |
385 | static const struct component_master_ops rockchip_drm_ops = { |
386 | .bind = rockchip_drm_bind, |
387 | .unbind = rockchip_drm_unbind, |
388 | }; |
389 | |
390 | static int rockchip_drm_platform_of_probe(struct device *dev) |
391 | { |
392 | struct device_node *np = dev->of_node; |
393 | struct device_node *port; |
394 | bool found = false; |
395 | int i; |
396 | |
397 | if (!np) |
398 | return -ENODEV; |
399 | |
400 | for (i = 0;; i++) { |
401 | port = of_parse_phandle(np, phandle_name: "ports" , index: i); |
402 | if (!port) |
403 | break; |
404 | |
405 | if (!of_device_is_available(device: port->parent)) { |
406 | of_node_put(node: port); |
407 | continue; |
408 | } |
409 | |
410 | found = true; |
411 | of_node_put(node: port); |
412 | } |
413 | |
414 | if (i == 0) { |
415 | DRM_DEV_ERROR(dev, "missing 'ports' property\n" ); |
416 | return -ENODEV; |
417 | } |
418 | |
419 | if (!found) { |
420 | DRM_DEV_ERROR(dev, |
421 | "No available vop found for display-subsystem.\n" ); |
422 | return -ENODEV; |
423 | } |
424 | |
425 | return 0; |
426 | } |
427 | |
428 | static int rockchip_drm_platform_probe(struct platform_device *pdev) |
429 | { |
430 | struct device *dev = &pdev->dev; |
431 | struct component_match *match = NULL; |
432 | int ret; |
433 | |
434 | ret = rockchip_drm_platform_of_probe(dev); |
435 | if (ret) |
436 | return ret; |
437 | |
438 | match = rockchip_drm_match_add(dev); |
439 | if (IS_ERR(ptr: match)) |
440 | return PTR_ERR(ptr: match); |
441 | |
442 | ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); |
443 | if (ret < 0) { |
444 | rockchip_drm_match_remove(dev); |
445 | return ret; |
446 | } |
447 | |
448 | return 0; |
449 | } |
450 | |
451 | static void rockchip_drm_platform_remove(struct platform_device *pdev) |
452 | { |
453 | component_master_del(&pdev->dev, &rockchip_drm_ops); |
454 | |
455 | rockchip_drm_match_remove(dev: &pdev->dev); |
456 | } |
457 | |
458 | static void rockchip_drm_platform_shutdown(struct platform_device *pdev) |
459 | { |
460 | struct drm_device *drm = platform_get_drvdata(pdev); |
461 | |
462 | if (drm) |
463 | drm_atomic_helper_shutdown(dev: drm); |
464 | } |
465 | |
466 | static const struct of_device_id rockchip_drm_dt_ids[] = { |
467 | { .compatible = "rockchip,display-subsystem" , }, |
468 | { /* sentinel */ }, |
469 | }; |
470 | MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids); |
471 | |
472 | static struct platform_driver rockchip_drm_platform_driver = { |
473 | .probe = rockchip_drm_platform_probe, |
474 | .remove_new = rockchip_drm_platform_remove, |
475 | .shutdown = rockchip_drm_platform_shutdown, |
476 | .driver = { |
477 | .name = "rockchip-drm" , |
478 | .of_match_table = rockchip_drm_dt_ids, |
479 | .pm = &rockchip_drm_pm_ops, |
480 | }, |
481 | }; |
482 | |
483 | #define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \ |
484 | if (IS_ENABLED(cond) && \ |
485 | !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \ |
486 | rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \ |
487 | } |
488 | |
489 | static int __init rockchip_drm_init(void) |
490 | { |
491 | int ret; |
492 | |
493 | if (drm_firmware_drivers_only()) |
494 | return -ENODEV; |
495 | |
496 | num_rockchip_sub_drivers = 0; |
497 | ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP); |
498 | ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2); |
499 | ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver, |
500 | CONFIG_ROCKCHIP_LVDS); |
501 | ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, |
502 | CONFIG_ROCKCHIP_ANALOGIX_DP); |
503 | ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP); |
504 | ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver, |
505 | CONFIG_ROCKCHIP_DW_HDMI); |
506 | ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver, |
507 | CONFIG_ROCKCHIP_DW_MIPI_DSI); |
508 | ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI); |
509 | ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver, |
510 | CONFIG_ROCKCHIP_RK3066_HDMI); |
511 | |
512 | ret = platform_register_drivers(rockchip_sub_drivers, |
513 | num_rockchip_sub_drivers); |
514 | if (ret) |
515 | return ret; |
516 | |
517 | ret = platform_driver_register(&rockchip_drm_platform_driver); |
518 | if (ret) |
519 | goto err_unreg_drivers; |
520 | |
521 | return 0; |
522 | |
523 | err_unreg_drivers: |
524 | platform_unregister_drivers(drivers: rockchip_sub_drivers, |
525 | count: num_rockchip_sub_drivers); |
526 | return ret; |
527 | } |
528 | |
529 | static void __exit rockchip_drm_fini(void) |
530 | { |
531 | platform_driver_unregister(&rockchip_drm_platform_driver); |
532 | |
533 | platform_unregister_drivers(drivers: rockchip_sub_drivers, |
534 | count: num_rockchip_sub_drivers); |
535 | } |
536 | |
537 | module_init(rockchip_drm_init); |
538 | module_exit(rockchip_drm_fini); |
539 | |
540 | MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>" ); |
541 | MODULE_DESCRIPTION("ROCKCHIP DRM Driver" ); |
542 | MODULE_LICENSE("GPL v2" ); |
543 | |