mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-09 23:34:42 +08:00
906dde0f35
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJZpRPIAAoJEAx081l5xIa+kCIP/2m2q0jBmCATvXXwrMBH0zNk 4lm9yIfl9pmluJP97aklvkeKF77chhost76+hv+0sQ9ZsJD8koHWv5WyTHEs7Cfn NpmtGPqYlIZsWNSwW0OFF/XzllgLCVEWa+W/7ryYzPZrSEZr6Ge4HE0qS3LfuLJv K89amZWHkP5ysPZ1uxRBzHtZfNAhdyjYVTUntCR7gj3DYv3yNdeZu+/epfcWK2w/ Q+ggoy644vX/yzy5L5zCGL/J1BjStDuec7sgAKTlNx4TwBUmp2wsfhEdovQBGFiu t5PHMajvrBRqSJWDIAZSUfjQzIMSz517J9LWeChU7KtAClNJQJEabbu4CoX4aEmG UbSzEe0IxnxQ4842jcqQXZ+mevlNIEIBVSNR7dXi17jL3Ts+APQgrYjRJYVk2ipg uQ9TwkeVVu2WRGyU8iRQrXAZI7+O3p4UnbNPjeG2qACD2Ur7Z3n7b0mhNFPOLzO4 gbIv4D6CcUB/vltl+vhZTW3P50oMCVSq8ScCpY8CGo29mZ5vypj5PTS+W8FsyY3Z ypyMqWg/DyxKlOoO+aK8EmXuZmgtDR4kb8asltH/S1A0NZkzjrFkKgs10Cp6EjJy Zz1BWa1KKEpdN6yp+jrbJKjf9MJ7K2RPGv3bxWnCCdNv4j49rk4t3IHqvcihddsd XXFQB5zE7Pz0ROi/VkXR =5fxW -----END PGP SIGNATURE----- Merge tag 'drm-for-v4.14' of git://people.freedesktop.org/~airlied/linux Pull drm updates from Dave Airlie: "This is the main drm pull request for 4.14 merge window. I'm sending this early, as my continuing journey into fatherhood is occurring really soon now, I'm going to be mostly useless for the next couple of weeks, though I may be able to read email, I doubt I'll be doing much patch applications or git sending. If anything urgent pops up I've asked Daniel/Jani/Alex/Sean to try and direct stuff towards you. Outside drm changes: Some rcar-du updates that touch the V4L tree, all acks should be in place. It adds one export to the radix tree code for new i915 use case. There are some minor AGP cleanups (don't see that too often). Changes to the vbox driver in staging to avoid breaking compilation. Summary: core: - Atomic helper fixes - Atomic UAPI fixes - Add YCBCR 4:2:0 support - Drop set_busid hook - Refactor fb_helper locking - Remove a bunch of internal APIs - Add a bunch of better default handlers - Format modifier/blob plane property added - More internal header refactoring - Make more internal API names consistent - Enhanced syncobj APIs (wait/signal/reset/create signalled) bridge: - Add Synopsys Designware MIPI DSI host bridge driver tiny: - Add Pervasive Displays RePaper displays - Add support for LEGO MINDSTORMS EV3 LCD i915: - Lots of GEN10/CNL support patches - drm syncobj support - Skylake+ watermark refactoring - GVT vGPU 48-bit ppgtt support - GVT performance improvements - NOA change ioctl - CCS (color compression) scanout support - GPU reset improvements amdgpu: - Initial hugepage support - BO migration logic rework - Vega10 improvements - Powerplay fixes - Stop reprogramming the MC - Fixes for ACP audio on stoney - SR-IOV fixes/improvements - Command submission overhead improvements amdkfd: - Non-dGPU upstreaming patches - Scratch VA ioctl - Image tiling modes - Update PM4 headers for new firmware - Drop all BUG_ONs. nouveau: - GP108 modesetting support. - Disable MSI on big endian. vmwgfx: - Add fence fd support. msm: - Runtime PM improvements exynos: - NV12MT support - Refactor KMS drivers imx-drm: - Lock scanout channel to improve memory bw - Cleanups etnaviv: - GEM object population fixes tegra: - Prep work for Tegra186 support - PRIME mmap support sunxi: - HDMI support improvements - HDMI CEC support omapdrm: - HDMI hotplug IRQ support - Big driver cleanup - OMAP5 DSI support rcar-du: - vblank fixes - VSP1 updates arcgpu: - Minor fixes stm: - Add STM32 DSI controller driver dw_hdmi: - Add support for Rockchip RK3399 - HDMI CEC support atmel-hlcdc: - Add 8-bit color support vc4: - Atomic fixes - New ioctl to attach a label to a buffer object - HDMI CEC support - Allow userspace to dictate rendering order on submit ioctl" * tag 'drm-for-v4.14' of git://people.freedesktop.org/~airlied/linux: (1074 commits) drm/syncobj: Add a signal ioctl (v3) drm/syncobj: Add a reset ioctl (v3) drm/syncobj: Add a syncobj_array_find helper drm/syncobj: Allow wait for submit and signal behavior (v5) drm/syncobj: Add a CREATE_SIGNALED flag drm/syncobj: Add a callback mechanism for replace_fence (v3) drm/syncobj: add sync obj wait interface. (v8) i915: Use drm_syncobj_fence_get drm/syncobj: Add a race-free drm_syncobj_fence_get helper (v2) drm/syncobj: Rename fence_get to find_fence drm: kirin: Add mode_valid logic to avoid mode clocks we can't generate drm/vmwgfx: Bump the version for fence FD support drm/vmwgfx: Add export fence to file descriptor support drm/vmwgfx: Add support for imported Fence File Descriptor drm/vmwgfx: Prepare to support fence fd drm/vmwgfx: Fix incorrect command header offset at restart drm/vmwgfx: Support the NOP_ERROR command drm/vmwgfx: Restart command buffers after errors drm/vmwgfx: Move irq bottom half processing to threads drm/vmwgfx: Don't use drm_irq_[un]install ...
496 lines
12 KiB
C
496 lines
12 KiB
C
/*
|
|
* Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
|
|
* Author:Mark Yao <mark.yao@rock-chips.com>
|
|
*
|
|
* based on exynos_drm_drv.c
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#include <drm/drm_of.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dma-iommu.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/component.h>
|
|
#include <linux/console.h>
|
|
#include <linux/iommu.h>
|
|
|
|
#include "rockchip_drm_drv.h"
|
|
#include "rockchip_drm_fb.h"
|
|
#include "rockchip_drm_fbdev.h"
|
|
#include "rockchip_drm_gem.h"
|
|
|
|
#define DRIVER_NAME "rockchip"
|
|
#define DRIVER_DESC "RockChip Soc DRM"
|
|
#define DRIVER_DATE "20140818"
|
|
#define DRIVER_MAJOR 1
|
|
#define DRIVER_MINOR 0
|
|
|
|
static bool is_support_iommu = true;
|
|
static struct drm_driver rockchip_drm_driver;
|
|
|
|
/*
|
|
* Attach a (component) device to the shared drm dma mapping from master drm
|
|
* device. This is used by the VOPs to map GEM buffers to a common DMA
|
|
* mapping.
|
|
*/
|
|
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
|
|
struct device *dev)
|
|
{
|
|
struct rockchip_drm_private *private = drm_dev->dev_private;
|
|
int ret;
|
|
|
|
if (!is_support_iommu)
|
|
return 0;
|
|
|
|
ret = iommu_attach_device(private->domain, dev);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to attach iommu device\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
|
|
struct device *dev)
|
|
{
|
|
struct rockchip_drm_private *private = drm_dev->dev_private;
|
|
struct iommu_domain *domain = private->domain;
|
|
|
|
if (!is_support_iommu)
|
|
return;
|
|
|
|
iommu_detach_device(domain, dev);
|
|
}
|
|
|
|
static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
|
|
{
|
|
struct rockchip_drm_private *private = drm_dev->dev_private;
|
|
struct iommu_domain_geometry *geometry;
|
|
u64 start, end;
|
|
|
|
if (!is_support_iommu)
|
|
return 0;
|
|
|
|
private->domain = iommu_domain_alloc(&platform_bus_type);
|
|
if (!private->domain)
|
|
return -ENOMEM;
|
|
|
|
geometry = &private->domain->geometry;
|
|
start = geometry->aperture_start;
|
|
end = geometry->aperture_end;
|
|
|
|
DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n",
|
|
start, end);
|
|
drm_mm_init(&private->mm, start, end - start + 1);
|
|
mutex_init(&private->mm_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
|
|
{
|
|
struct rockchip_drm_private *private = drm_dev->dev_private;
|
|
|
|
if (!is_support_iommu)
|
|
return;
|
|
|
|
drm_mm_takedown(&private->mm);
|
|
iommu_domain_free(private->domain);
|
|
}
|
|
|
|
static int rockchip_drm_bind(struct device *dev)
|
|
{
|
|
struct drm_device *drm_dev;
|
|
struct rockchip_drm_private *private;
|
|
int ret;
|
|
|
|
drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
|
|
if (IS_ERR(drm_dev))
|
|
return PTR_ERR(drm_dev);
|
|
|
|
dev_set_drvdata(dev, drm_dev);
|
|
|
|
private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
|
|
if (!private) {
|
|
ret = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
drm_dev->dev_private = private;
|
|
|
|
INIT_LIST_HEAD(&private->psr_list);
|
|
spin_lock_init(&private->psr_list_lock);
|
|
|
|
ret = rockchip_drm_init_iommu(drm_dev);
|
|
if (ret)
|
|
goto err_free;
|
|
|
|
drm_mode_config_init(drm_dev);
|
|
|
|
rockchip_drm_mode_config_init(drm_dev);
|
|
|
|
/* Try to bind all sub drivers. */
|
|
ret = component_bind_all(dev, drm_dev);
|
|
if (ret)
|
|
goto err_mode_config_cleanup;
|
|
|
|
ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
|
|
if (ret)
|
|
goto err_unbind_all;
|
|
|
|
drm_mode_config_reset(drm_dev);
|
|
|
|
/*
|
|
* enable drm irq mode.
|
|
* - with irq_enabled = true, we can use the vblank feature.
|
|
*/
|
|
drm_dev->irq_enabled = true;
|
|
|
|
ret = rockchip_drm_fbdev_init(drm_dev);
|
|
if (ret)
|
|
goto err_unbind_all;
|
|
|
|
/* init kms poll for handling hpd */
|
|
drm_kms_helper_poll_init(drm_dev);
|
|
|
|
ret = drm_dev_register(drm_dev, 0);
|
|
if (ret)
|
|
goto err_kms_helper_poll_fini;
|
|
|
|
return 0;
|
|
err_kms_helper_poll_fini:
|
|
drm_kms_helper_poll_fini(drm_dev);
|
|
rockchip_drm_fbdev_fini(drm_dev);
|
|
err_unbind_all:
|
|
component_unbind_all(dev, drm_dev);
|
|
err_mode_config_cleanup:
|
|
drm_mode_config_cleanup(drm_dev);
|
|
rockchip_iommu_cleanup(drm_dev);
|
|
err_free:
|
|
drm_dev->dev_private = NULL;
|
|
dev_set_drvdata(dev, NULL);
|
|
drm_dev_unref(drm_dev);
|
|
return ret;
|
|
}
|
|
|
|
static void rockchip_drm_unbind(struct device *dev)
|
|
{
|
|
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
|
|
|
drm_dev_unregister(drm_dev);
|
|
|
|
rockchip_drm_fbdev_fini(drm_dev);
|
|
drm_kms_helper_poll_fini(drm_dev);
|
|
|
|
drm_atomic_helper_shutdown(drm_dev);
|
|
component_unbind_all(dev, drm_dev);
|
|
drm_mode_config_cleanup(drm_dev);
|
|
rockchip_iommu_cleanup(drm_dev);
|
|
|
|
drm_dev->dev_private = NULL;
|
|
dev_set_drvdata(dev, NULL);
|
|
drm_dev_unref(drm_dev);
|
|
}
|
|
|
|
static void rockchip_drm_lastclose(struct drm_device *dev)
|
|
{
|
|
struct rockchip_drm_private *priv = dev->dev_private;
|
|
|
|
drm_fb_helper_restore_fbdev_mode_unlocked(&priv->fbdev_helper);
|
|
}
|
|
|
|
static const struct file_operations rockchip_drm_driver_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = drm_open,
|
|
.mmap = rockchip_gem_mmap,
|
|
.poll = drm_poll,
|
|
.read = drm_read,
|
|
.unlocked_ioctl = drm_ioctl,
|
|
.compat_ioctl = drm_compat_ioctl,
|
|
.release = drm_release,
|
|
};
|
|
|
|
static struct drm_driver rockchip_drm_driver = {
|
|
.driver_features = DRIVER_MODESET | DRIVER_GEM |
|
|
DRIVER_PRIME | DRIVER_ATOMIC,
|
|
.lastclose = rockchip_drm_lastclose,
|
|
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
|
.gem_free_object_unlocked = rockchip_gem_free_object,
|
|
.dumb_create = rockchip_gem_dumb_create,
|
|
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
|
|
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
|
|
.gem_prime_import = drm_gem_prime_import,
|
|
.gem_prime_export = drm_gem_prime_export,
|
|
.gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table,
|
|
.gem_prime_vmap = rockchip_gem_prime_vmap,
|
|
.gem_prime_vunmap = rockchip_gem_prime_vunmap,
|
|
.gem_prime_mmap = rockchip_gem_mmap_buf,
|
|
.fops = &rockchip_drm_driver_fops,
|
|
.name = DRIVER_NAME,
|
|
.desc = DRIVER_DESC,
|
|
.date = DRIVER_DATE,
|
|
.major = DRIVER_MAJOR,
|
|
.minor = DRIVER_MINOR,
|
|
};
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static void rockchip_drm_fb_suspend(struct drm_device *drm)
|
|
{
|
|
struct rockchip_drm_private *priv = drm->dev_private;
|
|
|
|
console_lock();
|
|
drm_fb_helper_set_suspend(&priv->fbdev_helper, 1);
|
|
console_unlock();
|
|
}
|
|
|
|
static void rockchip_drm_fb_resume(struct drm_device *drm)
|
|
{
|
|
struct rockchip_drm_private *priv = drm->dev_private;
|
|
|
|
console_lock();
|
|
drm_fb_helper_set_suspend(&priv->fbdev_helper, 0);
|
|
console_unlock();
|
|
}
|
|
|
|
static int rockchip_drm_sys_suspend(struct device *dev)
|
|
{
|
|
struct drm_device *drm = dev_get_drvdata(dev);
|
|
struct rockchip_drm_private *priv;
|
|
|
|
if (!drm)
|
|
return 0;
|
|
|
|
drm_kms_helper_poll_disable(drm);
|
|
rockchip_drm_fb_suspend(drm);
|
|
|
|
priv = drm->dev_private;
|
|
priv->state = drm_atomic_helper_suspend(drm);
|
|
if (IS_ERR(priv->state)) {
|
|
rockchip_drm_fb_resume(drm);
|
|
drm_kms_helper_poll_enable(drm);
|
|
return PTR_ERR(priv->state);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_drm_sys_resume(struct device *dev)
|
|
{
|
|
struct drm_device *drm = dev_get_drvdata(dev);
|
|
struct rockchip_drm_private *priv;
|
|
|
|
if (!drm)
|
|
return 0;
|
|
|
|
priv = drm->dev_private;
|
|
drm_atomic_helper_resume(drm, priv->state);
|
|
rockchip_drm_fb_resume(drm);
|
|
drm_kms_helper_poll_enable(drm);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops rockchip_drm_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
|
|
rockchip_drm_sys_resume)
|
|
};
|
|
|
|
#define MAX_ROCKCHIP_SUB_DRIVERS 16
|
|
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
|
|
static int num_rockchip_sub_drivers;
|
|
|
|
static int compare_dev(struct device *dev, void *data)
|
|
{
|
|
return dev == (struct device *)data;
|
|
}
|
|
|
|
static struct component_match *rockchip_drm_match_add(struct device *dev)
|
|
{
|
|
struct component_match *match = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < num_rockchip_sub_drivers; i++) {
|
|
struct platform_driver *drv = rockchip_sub_drivers[i];
|
|
struct device *p = NULL, *d;
|
|
|
|
do {
|
|
d = bus_find_device(&platform_bus_type, p, &drv->driver,
|
|
(void *)platform_bus_type.match);
|
|
put_device(p);
|
|
p = d;
|
|
|
|
if (!d)
|
|
break;
|
|
component_match_add(dev, &match, compare_dev, d);
|
|
} while (true);
|
|
}
|
|
|
|
return match ?: ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
static const struct component_master_ops rockchip_drm_ops = {
|
|
.bind = rockchip_drm_bind,
|
|
.unbind = rockchip_drm_unbind,
|
|
};
|
|
|
|
static int rockchip_drm_platform_of_probe(struct device *dev)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
struct device_node *port;
|
|
bool found = false;
|
|
int i;
|
|
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
for (i = 0;; i++) {
|
|
struct device_node *iommu;
|
|
|
|
port = of_parse_phandle(np, "ports", i);
|
|
if (!port)
|
|
break;
|
|
|
|
if (!of_device_is_available(port->parent)) {
|
|
of_node_put(port);
|
|
continue;
|
|
}
|
|
|
|
iommu = of_parse_phandle(port->parent, "iommus", 0);
|
|
if (!iommu || !of_device_is_available(iommu->parent)) {
|
|
dev_dbg(dev, "no iommu attached for %pOF, using non-iommu buffers\n",
|
|
port->parent);
|
|
/*
|
|
* if there is a crtc not support iommu, force set all
|
|
* crtc use non-iommu buffer.
|
|
*/
|
|
is_support_iommu = false;
|
|
}
|
|
|
|
found = true;
|
|
|
|
of_node_put(iommu);
|
|
of_node_put(port);
|
|
}
|
|
|
|
if (i == 0) {
|
|
dev_err(dev, "missing 'ports' property\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!found) {
|
|
dev_err(dev, "No available vop found for display-subsystem.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_drm_platform_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct component_match *match = NULL;
|
|
int ret;
|
|
|
|
ret = rockchip_drm_platform_of_probe(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
match = rockchip_drm_match_add(dev);
|
|
if (IS_ERR(match))
|
|
return PTR_ERR(match);
|
|
|
|
return component_master_add_with_match(dev, &rockchip_drm_ops, match);
|
|
}
|
|
|
|
static int rockchip_drm_platform_remove(struct platform_device *pdev)
|
|
{
|
|
component_master_del(&pdev->dev, &rockchip_drm_ops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id rockchip_drm_dt_ids[] = {
|
|
{ .compatible = "rockchip,display-subsystem", },
|
|
{ /* sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);
|
|
|
|
static struct platform_driver rockchip_drm_platform_driver = {
|
|
.probe = rockchip_drm_platform_probe,
|
|
.remove = rockchip_drm_platform_remove,
|
|
.driver = {
|
|
.name = "rockchip-drm",
|
|
.of_match_table = rockchip_drm_dt_ids,
|
|
.pm = &rockchip_drm_pm_ops,
|
|
},
|
|
};
|
|
|
|
#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
|
|
if (IS_ENABLED(cond) && \
|
|
!WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
|
|
rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
|
|
}
|
|
|
|
static int __init rockchip_drm_init(void)
|
|
{
|
|
int ret;
|
|
|
|
num_rockchip_sub_drivers = 0;
|
|
ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP);
|
|
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
|
|
CONFIG_ROCKCHIP_ANALOGIX_DP);
|
|
ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
|
|
ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
|
|
CONFIG_ROCKCHIP_DW_HDMI);
|
|
ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_driver,
|
|
CONFIG_ROCKCHIP_DW_MIPI_DSI);
|
|
ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
|
|
|
|
ret = platform_register_drivers(rockchip_sub_drivers,
|
|
num_rockchip_sub_drivers);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = platform_driver_register(&rockchip_drm_platform_driver);
|
|
if (ret)
|
|
goto err_unreg_drivers;
|
|
|
|
return 0;
|
|
|
|
err_unreg_drivers:
|
|
platform_unregister_drivers(rockchip_sub_drivers,
|
|
num_rockchip_sub_drivers);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit rockchip_drm_fini(void)
|
|
{
|
|
platform_driver_unregister(&rockchip_drm_platform_driver);
|
|
|
|
platform_unregister_drivers(rockchip_sub_drivers,
|
|
num_rockchip_sub_drivers);
|
|
}
|
|
|
|
module_init(rockchip_drm_init);
|
|
module_exit(rockchip_drm_fini);
|
|
|
|
MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
|
|
MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
|
|
MODULE_LICENSE("GPL v2");
|