forked from Minki/linux
8820b68bd3
Currently we are adding all components from the dts, if one of their drivers been disabled, we would not be able to bring up others. Refactor component match logic, follow exynos drm. Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com> Reviewed-by: Andrzej Hajda <a.hajda@samsung.com> Acked-by: Mark Yao <mark.yao@rock-chips.com> Tested-by: Heiko Stuebner <heiko@sntech.de> Signed-off-by: Sean Paul <seanpaul@chromium.org> Link: http://patchwork.freedesktop.org/patch/msgid/1490152880-21855-1-git-send-email-jeffy.chen@rock-chips.com
489 lines
12 KiB
C
489 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);
|
|
|
|
drm_mode_config_init(drm_dev);
|
|
|
|
rockchip_drm_mode_config_init(drm_dev);
|
|
|
|
ret = rockchip_drm_init_iommu(drm_dev);
|
|
if (ret)
|
|
goto err_config_cleanup;
|
|
|
|
/* Try to bind all sub drivers. */
|
|
ret = component_bind_all(dev, drm_dev);
|
|
if (ret)
|
|
goto err_iommu_cleanup;
|
|
|
|
/* init kms poll for handling hpd */
|
|
drm_kms_helper_poll_init(drm_dev);
|
|
|
|
/*
|
|
* enable drm irq mode.
|
|
* - with irq_enabled = true, we can use the vblank feature.
|
|
*/
|
|
drm_dev->irq_enabled = true;
|
|
|
|
ret = drm_vblank_init(drm_dev, ROCKCHIP_MAX_CRTC);
|
|
if (ret)
|
|
goto err_kms_helper_poll_fini;
|
|
|
|
drm_mode_config_reset(drm_dev);
|
|
|
|
ret = rockchip_drm_fbdev_init(drm_dev);
|
|
if (ret)
|
|
goto err_vblank_cleanup;
|
|
|
|
ret = drm_dev_register(drm_dev, 0);
|
|
if (ret)
|
|
goto err_fbdev_fini;
|
|
|
|
return 0;
|
|
err_fbdev_fini:
|
|
rockchip_drm_fbdev_fini(drm_dev);
|
|
err_vblank_cleanup:
|
|
drm_vblank_cleanup(drm_dev);
|
|
err_kms_helper_poll_fini:
|
|
drm_kms_helper_poll_fini(drm_dev);
|
|
component_unbind_all(dev, drm_dev);
|
|
err_iommu_cleanup:
|
|
rockchip_iommu_cleanup(drm_dev);
|
|
err_config_cleanup:
|
|
drm_mode_config_cleanup(drm_dev);
|
|
drm_dev->dev_private = NULL;
|
|
err_free:
|
|
drm_dev_unref(drm_dev);
|
|
return ret;
|
|
}
|
|
|
|
static void rockchip_drm_unbind(struct device *dev)
|
|
{
|
|
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
|
|
|
rockchip_drm_fbdev_fini(drm_dev);
|
|
drm_vblank_cleanup(drm_dev);
|
|
drm_kms_helper_poll_fini(drm_dev);
|
|
component_unbind_all(dev, drm_dev);
|
|
rockchip_iommu_cleanup(drm_dev);
|
|
drm_mode_config_cleanup(drm_dev);
|
|
drm_dev->dev_private = NULL;
|
|
drm_dev_unregister(drm_dev);
|
|
drm_dev_unref(drm_dev);
|
|
dev_set_drvdata(dev, NULL);
|
|
}
|
|
|
|
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,
|
|
.dumb_map_offset = rockchip_gem_dumb_map_offset,
|
|
.dumb_destroy = drm_gem_dumb_destroy,
|
|
.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 = drm->dev_private;
|
|
|
|
drm_kms_helper_poll_disable(drm);
|
|
rockchip_drm_fb_suspend(drm);
|
|
|
|
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 = 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 %s, using non-iommu buffers\n",
|
|
port->parent->full_name);
|
|
/*
|
|
* 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");
|