Commit | Line | Data |
---|---|---|
9c92ab61 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
2048e328 MY |
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 | |
2048e328 MY |
7 | */ |
8 | ||
2048e328 | 9 | #include <drm/drmP.h> |
2048e328 | 10 | #include <drm/drm_fb_helper.h> |
80f67cd8 | 11 | #include <drm/drm_gem_cma_helper.h> |
97ac0e47 | 12 | #include <drm/drm_of.h> |
fcd70cd3 | 13 | #include <drm/drm_probe_helper.h> |
2048e328 | 14 | #include <linux/dma-mapping.h> |
1aa5ca6e | 15 | #include <linux/dma-iommu.h> |
2048e328 | 16 | #include <linux/pm_runtime.h> |
00fe6148 | 17 | #include <linux/module.h> |
2048e328 | 18 | #include <linux/of_graph.h> |
3880f62e | 19 | #include <linux/of_platform.h> |
2048e328 | 20 | #include <linux/component.h> |
5a587383 | 21 | #include <linux/console.h> |
1aa5ca6e | 22 | #include <linux/iommu.h> |
2048e328 MY |
23 | |
24 | #include "rockchip_drm_drv.h" | |
25 | #include "rockchip_drm_fb.h" | |
26 | #include "rockchip_drm_fbdev.h" | |
27 | #include "rockchip_drm_gem.h" | |
28 | ||
29 | #define DRIVER_NAME "rockchip" | |
30 | #define DRIVER_DESC "RockChip Soc DRM" | |
31 | #define DRIVER_DATE "20140818" | |
32 | #define DRIVER_MAJOR 1 | |
33 | #define DRIVER_MINOR 0 | |
34 | ||
2d90d477 | 35 | static bool is_support_iommu = true; |
f706974a | 36 | static struct drm_driver rockchip_drm_driver; |
2d90d477 | 37 | |
2048e328 MY |
38 | /* |
39 | * Attach a (component) device to the shared drm dma mapping from master drm | |
40 | * device. This is used by the VOPs to map GEM buffers to a common DMA | |
41 | * mapping. | |
42 | */ | |
43 | int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, | |
44 | struct device *dev) | |
45 | { | |
1aa5ca6e | 46 | struct rockchip_drm_private *private = drm_dev->dev_private; |
2048e328 MY |
47 | int ret; |
48 | ||
2d90d477 MY |
49 | if (!is_support_iommu) |
50 | return 0; | |
51 | ||
1aa5ca6e SZ |
52 | ret = iommu_attach_device(private->domain, dev); |
53 | if (ret) { | |
d8dd6804 | 54 | DRM_DEV_ERROR(dev, "Failed to attach iommu device\n"); |
2048e328 | 55 | return ret; |
1aa5ca6e | 56 | } |
2048e328 | 57 | |
1aa5ca6e | 58 | return 0; |
2048e328 | 59 | } |
2048e328 MY |
60 | |
61 | void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, | |
62 | struct device *dev) | |
63 | { | |
1aa5ca6e SZ |
64 | struct rockchip_drm_private *private = drm_dev->dev_private; |
65 | struct iommu_domain *domain = private->domain; | |
66 | ||
2d90d477 MY |
67 | if (!is_support_iommu) |
68 | return; | |
69 | ||
1aa5ca6e | 70 | iommu_detach_device(domain, dev); |
2048e328 | 71 | } |
2048e328 | 72 | |
1aa5ca6e SZ |
73 | static int rockchip_drm_init_iommu(struct drm_device *drm_dev) |
74 | { | |
75 | struct rockchip_drm_private *private = drm_dev->dev_private; | |
76 | struct iommu_domain_geometry *geometry; | |
77 | u64 start, end; | |
78 | ||
79 | if (!is_support_iommu) | |
80 | return 0; | |
81 | ||
82 | private->domain = iommu_domain_alloc(&platform_bus_type); | |
83 | if (!private->domain) | |
84 | return -ENOMEM; | |
85 | ||
86 | geometry = &private->domain->geometry; | |
87 | start = geometry->aperture_start; | |
88 | end = geometry->aperture_end; | |
89 | ||
90 | DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n", | |
91 | start, end); | |
92 | drm_mm_init(&private->mm, start, end - start + 1); | |
93 | mutex_init(&private->mm_lock); | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | static void rockchip_iommu_cleanup(struct drm_device *drm_dev) | |
99 | { | |
100 | struct rockchip_drm_private *private = drm_dev->dev_private; | |
101 | ||
102 | if (!is_support_iommu) | |
103 | return; | |
104 | ||
105 | drm_mm_takedown(&private->mm); | |
106 | iommu_domain_free(private->domain); | |
2048e328 | 107 | } |
2048e328 | 108 | |
f706974a | 109 | static int rockchip_drm_bind(struct device *dev) |
2048e328 | 110 | { |
f706974a | 111 | struct drm_device *drm_dev; |
2048e328 | 112 | struct rockchip_drm_private *private; |
2048e328 MY |
113 | int ret; |
114 | ||
f706974a | 115 | drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev); |
0f288605 TG |
116 | if (IS_ERR(drm_dev)) |
117 | return PTR_ERR(drm_dev); | |
2048e328 | 118 | |
f706974a TV |
119 | dev_set_drvdata(dev, drm_dev); |
120 | ||
121 | private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL); | |
122 | if (!private) { | |
123 | ret = -ENOMEM; | |
9127f99c | 124 | goto err_free; |
f706974a TV |
125 | } |
126 | ||
2048e328 MY |
127 | drm_dev->dev_private = private; |
128 | ||
5182c1a5 | 129 | INIT_LIST_HEAD(&private->psr_list); |
60beeccc | 130 | mutex_init(&private->psr_list_lock); |
5182c1a5 | 131 | |
ccea9199 JC |
132 | ret = rockchip_drm_init_iommu(drm_dev); |
133 | if (ret) | |
134 | goto err_free; | |
135 | ||
2048e328 MY |
136 | drm_mode_config_init(drm_dev); |
137 | ||
138 | rockchip_drm_mode_config_init(drm_dev); | |
139 | ||
2048e328 MY |
140 | /* Try to bind all sub drivers. */ |
141 | ret = component_bind_all(dev, drm_dev); | |
142 | if (ret) | |
ccea9199 | 143 | goto err_mode_config_cleanup; |
2048e328 | 144 | |
ccea9199 JC |
145 | ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc); |
146 | if (ret) | |
147 | goto err_unbind_all; | |
148 | ||
149 | drm_mode_config_reset(drm_dev); | |
2048e328 MY |
150 | |
151 | /* | |
152 | * enable drm irq mode. | |
153 | * - with irq_enabled = true, we can use the vblank feature. | |
154 | */ | |
155 | drm_dev->irq_enabled = true; | |
156 | ||
2048e328 MY |
157 | ret = rockchip_drm_fbdev_init(drm_dev); |
158 | if (ret) | |
8415ab56 M |
159 | goto err_unbind_all; |
160 | ||
161 | /* init kms poll for handling hpd */ | |
162 | drm_kms_helper_poll_init(drm_dev); | |
2048e328 | 163 | |
9127f99c TF |
164 | ret = drm_dev_register(drm_dev, 0); |
165 | if (ret) | |
8415ab56 | 166 | goto err_kms_helper_poll_fini; |
9127f99c | 167 | |
2048e328 | 168 | return 0; |
2048e328 MY |
169 | err_kms_helper_poll_fini: |
170 | drm_kms_helper_poll_fini(drm_dev); | |
8415ab56 | 171 | rockchip_drm_fbdev_fini(drm_dev); |
ccea9199 | 172 | err_unbind_all: |
2048e328 | 173 | component_unbind_all(dev, drm_dev); |
ccea9199 | 174 | err_mode_config_cleanup: |
2048e328 | 175 | drm_mode_config_cleanup(drm_dev); |
ccea9199 | 176 | rockchip_iommu_cleanup(drm_dev); |
f706974a | 177 | err_free: |
ccea9199 JC |
178 | drm_dev->dev_private = NULL; |
179 | dev_set_drvdata(dev, NULL); | |
574e0fbf | 180 | drm_dev_put(drm_dev); |
2048e328 MY |
181 | return ret; |
182 | } | |
183 | ||
f706974a | 184 | static void rockchip_drm_unbind(struct device *dev) |
2048e328 | 185 | { |
f706974a | 186 | struct drm_device *drm_dev = dev_get_drvdata(dev); |
2048e328 | 187 | |
ccea9199 JC |
188 | drm_dev_unregister(drm_dev); |
189 | ||
2048e328 | 190 | rockchip_drm_fbdev_fini(drm_dev); |
2048e328 | 191 | drm_kms_helper_poll_fini(drm_dev); |
ccea9199 | 192 | |
c1bb8188 | 193 | drm_atomic_helper_shutdown(drm_dev); |
2048e328 | 194 | component_unbind_all(dev, drm_dev); |
2048e328 | 195 | drm_mode_config_cleanup(drm_dev); |
ccea9199 JC |
196 | rockchip_iommu_cleanup(drm_dev); |
197 | ||
2048e328 | 198 | drm_dev->dev_private = NULL; |
f706974a | 199 | dev_set_drvdata(dev, NULL); |
574e0fbf | 200 | drm_dev_put(drm_dev); |
2048e328 MY |
201 | } |
202 | ||
2048e328 MY |
203 | static const struct file_operations rockchip_drm_driver_fops = { |
204 | .owner = THIS_MODULE, | |
205 | .open = drm_open, | |
206 | .mmap = rockchip_gem_mmap, | |
207 | .poll = drm_poll, | |
208 | .read = drm_read, | |
209 | .unlocked_ioctl = drm_ioctl, | |
2048e328 | 210 | .compat_ioctl = drm_compat_ioctl, |
2048e328 MY |
211 | .release = drm_release, |
212 | }; | |
213 | ||
2048e328 | 214 | static struct drm_driver rockchip_drm_driver = { |
63ebb9fa MY |
215 | .driver_features = DRIVER_MODESET | DRIVER_GEM | |
216 | DRIVER_PRIME | DRIVER_ATOMIC, | |
33e9d038 | 217 | .lastclose = drm_fb_helper_lastclose, |
80f67cd8 | 218 | .gem_vm_ops = &drm_gem_cma_vm_ops, |
c2466ac3 | 219 | .gem_free_object_unlocked = rockchip_gem_free_object, |
2048e328 | 220 | .dumb_create = rockchip_gem_dumb_create, |
2048e328 MY |
221 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, |
222 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
223 | .gem_prime_import = drm_gem_prime_import, | |
224 | .gem_prime_export = drm_gem_prime_export, | |
225 | .gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table, | |
6fd0bfe2 | 226 | .gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table, |
2048e328 MY |
227 | .gem_prime_vmap = rockchip_gem_prime_vmap, |
228 | .gem_prime_vunmap = rockchip_gem_prime_vunmap, | |
229 | .gem_prime_mmap = rockchip_gem_mmap_buf, | |
230 | .fops = &rockchip_drm_driver_fops, | |
231 | .name = DRIVER_NAME, | |
232 | .desc = DRIVER_DESC, | |
233 | .date = DRIVER_DATE, | |
234 | .major = DRIVER_MAJOR, | |
235 | .minor = DRIVER_MINOR, | |
236 | }; | |
237 | ||
238 | #ifdef CONFIG_PM_SLEEP | |
5a587383 TV |
239 | static int rockchip_drm_sys_suspend(struct device *dev) |
240 | { | |
241 | struct drm_device *drm = dev_get_drvdata(dev); | |
5a587383 | 242 | |
e7941cc2 | 243 | return drm_mode_config_helper_suspend(drm); |
2048e328 MY |
244 | } |
245 | ||
246 | static int rockchip_drm_sys_resume(struct device *dev) | |
247 | { | |
248 | struct drm_device *drm = dev_get_drvdata(dev); | |
0fa375e6 | 249 | |
e7941cc2 | 250 | return drm_mode_config_helper_resume(drm); |
2048e328 MY |
251 | } |
252 | #endif | |
253 | ||
254 | static const struct dev_pm_ops rockchip_drm_pm_ops = { | |
255 | SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend, | |
256 | rockchip_drm_sys_resume) | |
257 | }; | |
258 | ||
8820b68b JC |
259 | #define MAX_ROCKCHIP_SUB_DRIVERS 16 |
260 | static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS]; | |
261 | static int num_rockchip_sub_drivers; | |
2048e328 | 262 | |
3880f62e HS |
263 | /* |
264 | * Check if a vop endpoint is leading to a rockchip subdriver or bridge. | |
265 | * Should be called from the component bind stage of the drivers | |
266 | * to ensure that all subdrivers are probed. | |
267 | * | |
268 | * @ep: endpoint of a rockchip vop | |
269 | * | |
270 | * returns true if subdriver, false if external bridge and -ENODEV | |
271 | * if remote port does not contain a device. | |
272 | */ | |
273 | int rockchip_drm_endpoint_is_subdriver(struct device_node *ep) | |
274 | { | |
275 | struct device_node *node = of_graph_get_remote_port_parent(ep); | |
276 | struct platform_device *pdev; | |
277 | struct device_driver *drv; | |
278 | int i; | |
279 | ||
280 | if (!node) | |
281 | return -ENODEV; | |
282 | ||
283 | /* status disabled will prevent creation of platform-devices */ | |
284 | pdev = of_find_device_by_node(node); | |
285 | of_node_put(node); | |
286 | if (!pdev) | |
287 | return -ENODEV; | |
288 | ||
289 | /* | |
290 | * All rockchip subdrivers have probed at this point, so | |
291 | * any device not having a driver now is an external bridge. | |
292 | */ | |
293 | drv = pdev->dev.driver; | |
294 | if (!drv) { | |
295 | platform_device_put(pdev); | |
296 | return false; | |
297 | } | |
298 | ||
299 | for (i = 0; i < num_rockchip_sub_drivers; i++) { | |
300 | if (rockchip_sub_drivers[i] == to_platform_driver(drv)) { | |
301 | platform_device_put(pdev); | |
302 | return true; | |
303 | } | |
304 | } | |
305 | ||
306 | platform_device_put(pdev); | |
307 | return false; | |
308 | } | |
309 | ||
8820b68b JC |
310 | static int compare_dev(struct device *dev, void *data) |
311 | { | |
312 | return dev == (struct device *)data; | |
2048e328 MY |
313 | } |
314 | ||
7d4e981d JC |
315 | static void rockchip_drm_match_remove(struct device *dev) |
316 | { | |
317 | struct device_link *link; | |
318 | ||
319 | list_for_each_entry(link, &dev->links.consumers, s_node) | |
320 | device_link_del(link); | |
321 | } | |
322 | ||
8820b68b | 323 | static struct component_match *rockchip_drm_match_add(struct device *dev) |
5bad7d29 | 324 | { |
8820b68b JC |
325 | struct component_match *match = NULL; |
326 | int i; | |
5bad7d29 | 327 | |
8820b68b JC |
328 | for (i = 0; i < num_rockchip_sub_drivers; i++) { |
329 | struct platform_driver *drv = rockchip_sub_drivers[i]; | |
330 | struct device *p = NULL, *d; | |
331 | ||
332 | do { | |
333 | d = bus_find_device(&platform_bus_type, p, &drv->driver, | |
334 | (void *)platform_bus_type.match); | |
335 | put_device(p); | |
336 | p = d; | |
5bad7d29 | 337 | |
8820b68b JC |
338 | if (!d) |
339 | break; | |
7d4e981d JC |
340 | |
341 | device_link_add(dev, d, DL_FLAG_STATELESS); | |
8820b68b JC |
342 | component_match_add(dev, &match, compare_dev, d); |
343 | } while (true); | |
5bad7d29 | 344 | } |
8820b68b | 345 | |
7d4e981d JC |
346 | if (IS_ERR(match)) |
347 | rockchip_drm_match_remove(dev); | |
348 | ||
8820b68b | 349 | return match ?: ERR_PTR(-ENODEV); |
5bad7d29 MY |
350 | } |
351 | ||
2048e328 MY |
352 | static const struct component_master_ops rockchip_drm_ops = { |
353 | .bind = rockchip_drm_bind, | |
354 | .unbind = rockchip_drm_unbind, | |
355 | }; | |
356 | ||
8820b68b | 357 | static int rockchip_drm_platform_of_probe(struct device *dev) |
2048e328 | 358 | { |
5bad7d29 MY |
359 | struct device_node *np = dev->of_node; |
360 | struct device_node *port; | |
8820b68b | 361 | bool found = false; |
5bad7d29 | 362 | int i; |
2048e328 | 363 | |
5bad7d29 | 364 | if (!np) |
2048e328 | 365 | return -ENODEV; |
8820b68b | 366 | |
5bad7d29 | 367 | for (i = 0;; i++) { |
2d90d477 MY |
368 | struct device_node *iommu; |
369 | ||
5bad7d29 MY |
370 | port = of_parse_phandle(np, "ports", i); |
371 | if (!port) | |
372 | break; | |
373 | ||
374 | if (!of_device_is_available(port->parent)) { | |
375 | of_node_put(port); | |
376 | continue; | |
377 | } | |
2048e328 | 378 | |
2d90d477 MY |
379 | iommu = of_parse_phandle(port->parent, "iommus", 0); |
380 | if (!iommu || !of_device_is_available(iommu->parent)) { | |
d8dd6804 HM |
381 | DRM_DEV_DEBUG(dev, |
382 | "no iommu attached for %pOF, using non-iommu buffers\n", | |
383 | port->parent); | |
2d90d477 MY |
384 | /* |
385 | * if there is a crtc not support iommu, force set all | |
386 | * crtc use non-iommu buffer. | |
387 | */ | |
388 | is_support_iommu = false; | |
389 | } | |
390 | ||
8820b68b JC |
391 | found = true; |
392 | ||
6d5fa28c | 393 | of_node_put(iommu); |
5bad7d29 MY |
394 | of_node_put(port); |
395 | } | |
396 | ||
397 | if (i == 0) { | |
d8dd6804 | 398 | DRM_DEV_ERROR(dev, "missing 'ports' property\n"); |
5bad7d29 MY |
399 | return -ENODEV; |
400 | } | |
401 | ||
8820b68b | 402 | if (!found) { |
d8dd6804 HM |
403 | DRM_DEV_ERROR(dev, |
404 | "No available vop found for display-subsystem.\n"); | |
5bad7d29 MY |
405 | return -ENODEV; |
406 | } | |
5bad7d29 | 407 | |
8820b68b JC |
408 | return 0; |
409 | } | |
5bad7d29 | 410 | |
8820b68b JC |
411 | static int rockchip_drm_platform_probe(struct platform_device *pdev) |
412 | { | |
413 | struct device *dev = &pdev->dev; | |
414 | struct component_match *match = NULL; | |
415 | int ret; | |
416 | ||
417 | ret = rockchip_drm_platform_of_probe(dev); | |
418 | if (ret) | |
419 | return ret; | |
420 | ||
421 | match = rockchip_drm_match_add(dev); | |
422 | if (IS_ERR(match)) | |
423 | return PTR_ERR(match); | |
5bad7d29 | 424 | |
7d4e981d JC |
425 | ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); |
426 | if (ret < 0) { | |
427 | rockchip_drm_match_remove(dev); | |
428 | return ret; | |
429 | } | |
430 | ||
431 | return 0; | |
2048e328 MY |
432 | } |
433 | ||
434 | static int rockchip_drm_platform_remove(struct platform_device *pdev) | |
435 | { | |
436 | component_master_del(&pdev->dev, &rockchip_drm_ops); | |
437 | ||
7d4e981d JC |
438 | rockchip_drm_match_remove(&pdev->dev); |
439 | ||
2048e328 MY |
440 | return 0; |
441 | } | |
442 | ||
b8f9d7f3 VB |
443 | static void rockchip_drm_platform_shutdown(struct platform_device *pdev) |
444 | { | |
445 | struct drm_device *drm = platform_get_drvdata(pdev); | |
446 | ||
447 | if (drm) | |
448 | drm_atomic_helper_shutdown(drm); | |
449 | } | |
450 | ||
2048e328 MY |
451 | static const struct of_device_id rockchip_drm_dt_ids[] = { |
452 | { .compatible = "rockchip,display-subsystem", }, | |
453 | { /* sentinel */ }, | |
454 | }; | |
455 | MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids); | |
456 | ||
457 | static struct platform_driver rockchip_drm_platform_driver = { | |
458 | .probe = rockchip_drm_platform_probe, | |
459 | .remove = rockchip_drm_platform_remove, | |
b8f9d7f3 | 460 | .shutdown = rockchip_drm_platform_shutdown, |
2048e328 | 461 | .driver = { |
2048e328 MY |
462 | .name = "rockchip-drm", |
463 | .of_match_table = rockchip_drm_dt_ids, | |
464 | .pm = &rockchip_drm_pm_ops, | |
465 | }, | |
466 | }; | |
467 | ||
8820b68b JC |
468 | #define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \ |
469 | if (IS_ENABLED(cond) && \ | |
470 | !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \ | |
471 | rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \ | |
472 | } | |
473 | ||
474 | static int __init rockchip_drm_init(void) | |
475 | { | |
476 | int ret; | |
477 | ||
478 | num_rockchip_sub_drivers = 0; | |
479 | ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP); | |
34cc0aa2 SH |
480 | ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver, |
481 | CONFIG_ROCKCHIP_LVDS); | |
8820b68b JC |
482 | ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, |
483 | CONFIG_ROCKCHIP_ANALOGIX_DP); | |
484 | ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP); | |
485 | ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver, | |
486 | CONFIG_ROCKCHIP_DW_HDMI); | |
2d4f7bda | 487 | ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver, |
8820b68b JC |
488 | CONFIG_ROCKCHIP_DW_MIPI_DSI); |
489 | ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI); | |
f84d3d37 ZY |
490 | ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver, |
491 | CONFIG_ROCKCHIP_RK3066_HDMI); | |
8820b68b JC |
492 | |
493 | ret = platform_register_drivers(rockchip_sub_drivers, | |
494 | num_rockchip_sub_drivers); | |
495 | if (ret) | |
496 | return ret; | |
497 | ||
498 | ret = platform_driver_register(&rockchip_drm_platform_driver); | |
499 | if (ret) | |
500 | goto err_unreg_drivers; | |
501 | ||
502 | return 0; | |
503 | ||
504 | err_unreg_drivers: | |
505 | platform_unregister_drivers(rockchip_sub_drivers, | |
506 | num_rockchip_sub_drivers); | |
507 | return ret; | |
508 | } | |
509 | ||
510 | static void __exit rockchip_drm_fini(void) | |
511 | { | |
512 | platform_driver_unregister(&rockchip_drm_platform_driver); | |
513 | ||
514 | platform_unregister_drivers(rockchip_sub_drivers, | |
515 | num_rockchip_sub_drivers); | |
516 | } | |
517 | ||
518 | module_init(rockchip_drm_init); | |
519 | module_exit(rockchip_drm_fini); | |
2048e328 MY |
520 | |
521 | MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>"); | |
522 | MODULE_DESCRIPTION("ROCKCHIP DRM Driver"); | |
523 | MODULE_LICENSE("GPL v2"); |