* [RESENT PATCH v7 0/3] Add drm driver for Rockchip Socs
@ 2014-09-26 10:53 Mark Yao
[not found] ` <1411728800-25112-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Mark Yao @ 2014-09-26 10:53 UTC (permalink / raw)
To: heiko-4mtYJXux2i+zQB+pC5nmwQ, Boris BREZILLON, David Airlie,
Rob Clark, Daniel Vetter, Rob Herring, Pawel Moll, Mark Rutland,
Ian Campbell, Kumar Gala, Randy Dunlap, Grant Likely,
Greg Kroah-Hartman, John Stultz, Rom Lemarchand
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
dianders-F7+t8E8rja9g9hUCZPvPmw, marcheu-F7+t8E8rja9g9hUCZPvPmw,
dbehr-F7+t8E8rja9g9hUCZPvPmw, olof-nZhT3qVonbNeoWH0uzbU5w,
djkurtz-F7+t8E8rja9g9hUCZPvPmw, xjq-TNX95d0MmH7DzftRWevZcw,
kfx-TNX95d0MmH7DzftRWevZcw, cym-TNX95d0MmH7DzftRWevZcw,
cf-TNX95d0MmH7DzftRWevZcw, zyw-TNX95d0MmH7DzftRWevZcw,
xxm-TNX95d0MmH7DzftRWevZcw, huangtao-TNX95d0MmH7DzftRWevZcw,
kever.yang-TNX95d0MmH7DzftRWevZcw, yxj-TNX95d0MmH7DzftRWevZcw,
wxt-TNX95d0MmH7DzftRWevZcw, xw-TNX95d0MmH7DzftRWevZcw, Mark Yao
This a series of patches is a DRM Driver for Rockchip Socs, add support
for vop devices. Future patches will add additional encoders/connectors,
such as eDP, HDMI.
The basic "crtc" for rockchip is a "VOP" - Video Output Processor.
the vop devices found on Rockchip rk3288 Soc, rk3288 soc have two similar
Vop devices. Vop devices support iommu mapping, we use dma-mapping API with
ARM_DMA_USE_IOMMU.
Resent v7 email to fix mail style.
Changes in v2:
- add DRM master device node to list all display nodes that comprise
the graphics subsystem.
- use the component framework to defer main drm driver probe
until all VOP devices have been probed.
- use dma-mapping API with ARM_DMA_USE_IOMMU, create dma mapping by
master device and each vop device can shared the drm dma mapping.
- use drm_crtc_init_with_planes and drm_universal_plane_init.
- remove unnecessary middle layers.
- add cursor set, move funcs to rockchip drm crtc.
- add vop reset.
Changes in v3:
- change "crtc->fb" to "crtc->primary-fb"
Adviced by Daniel Vetter
- init cursor plane with universal api, remove unnecessary cursor set,move
Changes in v4:
Adviced by David Herrmann
- remove drm_platform_*() usage, use register drm device directly.
Adviced by Rob Clark
- remove special mmap ioctl, do userspace mmap with normal mmap() or mmap offset
Changes in v5:
Adviced by Arnd Bergmann
- doing DMA start with a 32-bit masks with dma_mask and dma_coherent_mark
- fix some incorrect dependencies.
Adviced by Boris BREZILLON
- fix some mistake and bugs.
Adviced by Daniel Vetter
- drop all special ioctl and use generic kms ioctl instead.
Adviced by Rob Clark
- use unlocked api for drm_fb_helper_restore_fbdev_mode.
- remove unused rockchip_gem_prime_import_sg_table.
Changes in v6:
- set gem buffer pitch 64 bytes align, needed by mali gpu.
Adviced by Daniel Kurtz
- fix some mistake, bugs, remove unused define, more better code style etc.
- use clk_prepare()/unprepare() at probe()/remove() and clk_enable()/disable()
at runtime instead of clk_prepare_enable().
- provide a help function from vop for encoder to do mode config, instead of
using drm_diaplay_mode private method.
- change vop mode_set timing to make it more safely.
Changes in v7:
- fix memory leakage problem.
Mark yao (3):
drm: rockchip: Add basic drm driver
dt-bindings: video: Add for rockchip display subsytem
dt-bindings: video: Add documentation for rockchip vop
.../devicetree/bindings/video/rockchip-drm.txt | 19 +
.../devicetree/bindings/video/rockchip-vop.txt | 58 +
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/rockchip/Kconfig | 17 +
drivers/gpu/drm/rockchip/Makefile | 8 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 516 +++++++
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 61 +
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 +++
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 230 ++++
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 20 +
drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 345 +++++
drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 55 +
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1422 ++++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 196 +++
16 files changed, 3179 insertions(+)
--
1.7.9.5
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RESENT PATCH v7 1/3] drm: rockchip: Add basic drm driver
[not found] ` <1411728800-25112-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
@ 2014-09-26 10:55 ` Mark Yao
[not found] ` <1411728904-25182-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
0 siblings, 1 reply; 6+ messages in thread
From: Mark Yao @ 2014-09-26 10:55 UTC (permalink / raw)
To: heiko-4mtYJXux2i+zQB+pC5nmwQ, Boris BREZILLON, David Airlie,
Rob Clark, Daniel Vetter, Rob Herring, Pawel Moll, Mark Rutland,
Ian Campbell, Kumar Gala, Randy Dunlap, Grant Likely,
Greg Kroah-Hartman, John Stultz, Rom Lemarchand
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
dianders-F7+t8E8rja9g9hUCZPvPmw, marcheu-F7+t8E8rja9g9hUCZPvPmw,
dbehr-F7+t8E8rja9g9hUCZPvPmw, olof-nZhT3qVonbNeoWH0uzbU5w,
djkurtz-F7+t8E8rja9g9hUCZPvPmw, xjq-TNX95d0MmH7DzftRWevZcw,
kfx-TNX95d0MmH7DzftRWevZcw, cym-TNX95d0MmH7DzftRWevZcw,
cf-TNX95d0MmH7DzftRWevZcw, zyw-TNX95d0MmH7DzftRWevZcw,
xxm-TNX95d0MmH7DzftRWevZcw, huangtao-TNX95d0MmH7DzftRWevZcw,
kever.yang-TNX95d0MmH7DzftRWevZcw, yxj-TNX95d0MmH7DzftRWevZcw,
wxt-TNX95d0MmH7DzftRWevZcw, xw-TNX95d0MmH7DzftRWevZcw, Mark yao
From: Mark yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
This patch adds the basic structure of a DRM Driver for Rockchip Socs.
Signed-off-by: Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Signed-off-by: Daniel Kurtz <djkurtz-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
Acked-by: Daniel Vetter <daniel-/w4YWyX8dFk@public.gmane.org>
Reviewed-by: Rob Clark <robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
Changes in v2:
- use the component framework to defer main drm driver probe
until all VOP devices have been probed.
- use dma-mapping API with ARM_DMA_USE_IOMMU, create dma mapping by
master device and each vop device can shared the drm dma mapping.
- use drm_crtc_init_with_planes and drm_universal_plane_init.
- remove unnecessary middle layers.
- add cursor set, move funcs to rockchip drm crtc.
- use vop reset at first init
- reference framebuffer when used and unreference when swap out vop
Changes in v3:
- change "crtc->fb" to "crtc->primary-fb"
Adviced by Daniel Vetter
- init cursor plane with universal api, remove unnecessary cursor set,move
Changes in v4:
Adviced by David Herrmann
- remove drm_platform_*() usage, use register drm device directly.
Adviced by Rob Clark
- remove special mmap ioctl, do userspace mmap with normal mmap() or mmap offset
Changes in v5:
Adviced by Arnd Bergmann
- doing DMA start with a 32-bit masks with dma_mask and dma_coherent_mark
- fix some incorrect dependencies.
Adviced by Boris BREZILLON
- fix some mistake and bugs.
Adviced by Daniel Vetter
- drop all special ioctl and use generic kms ioctl instead.
Adviced by Rob Clark
- use unlocked api for drm_fb_helper_restore_fbdev_mode.
- remove unused rockchip_gem_prime_import_sg_table.
Changes in v6:
- set gem buffer pitch 64 bytes align, needed by mali gpu.
Adviced by Daniel Kurtz
- fix some mistake, bugs, remove unused define, more better code style etc.
- use clk_prepare()/unprepare() at probe()/remove() and clk_enable()/disable()
at runtime instead of clk_prepare_enable().
- provide a help function from vop for encoder to do mode config, instead of
using drm_diaplay_mode private method.
- change vop mode_set timing to make it more safely.
Changes in v7:
- fix memory leakage problem
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/rockchip/Kconfig | 17 +
drivers/gpu/drm/rockchip/Makefile | 8 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 516 +++++++++
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 61 ++
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 ++++
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 230 ++++
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 20 +
drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 345 ++++++
drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 55 +
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1422 +++++++++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 196 ++++
14 files changed, 3102 insertions(+)
create mode 100644 drivers/gpu/drm/rockchip/Kconfig
create mode 100644 drivers/gpu/drm/rockchip/Makefile
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index b066bb3..7c4c3c6 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -171,6 +171,8 @@ config DRM_SAVAGE
source "drivers/gpu/drm/exynos/Kconfig"
+source "drivers/gpu/drm/rockchip/Kconfig"
+
source "drivers/gpu/drm/vmwgfx/Kconfig"
source "drivers/gpu/drm/gma500/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 4a55d59..d03387a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/
obj-$(CONFIG_DRM_VIA) +=via/
obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
obj-$(CONFIG_DRM_EXYNOS) +=exynos/
+obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
new file mode 100644
index 0000000..87255f7
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -0,0 +1,17 @@
+config DRM_ROCKCHIP
+ tristate "DRM Support for Rockchip"
+ depends on DRM && ROCKCHIP_IOMMU && ARM_DMA_USE_IOMMU && IOMMU_API
+ select DRM_KMS_HELPER
+ select DRM_KMS_FB_HELPER
+ select DRM_PANEL
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+ select VIDEOMODE_HELPERS
+ help
+ Choose this option if you have a Rockchip soc chipset.
+ This driver provides kernel mode setting and buffer
+ management to userspace. This driver does not provides
+ 2D or 3D acceleration; acceleration is performed by other
+ IP found on the SoC.
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
new file mode 100644
index 0000000..b3a5193
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the drm device driver. This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o rockchip_drm_fbdev.o \
+ rockchip_drm_gem.o rockchip_drm_vop.o
+
+obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
new file mode 100644
index 0000000..a571acc
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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 <asm/dma-iommu.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_graph.h>
+#include <linux/component.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
+
+/*
+ * 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 dma_iommu_mapping *mapping = drm_dev->dev->archdata.mapping;
+ int ret;
+
+ ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ dma_set_max_seg_size(dev, 0xffffffffu);
+
+ return arm_iommu_attach_device(dev, mapping);
+}
+
+void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ arm_iommu_detach_device(drm_dev->dev);
+}
+
+static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags)
+{
+ struct rockchip_drm_private *private;
+ struct dma_iommu_mapping *mapping;
+ struct device *dev = drm_dev->dev;
+ int ret;
+
+ private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
+ if (!private)
+ return -ENOMEM;
+
+ drm_dev->dev_private = private;
+
+ drm_mode_config_init(drm_dev);
+
+ rockchip_drm_mode_config_init(drm_dev);
+
+ dev->dma_parms = devm_kzalloc(dev, sizeof(*dev->dma_parms),
+ GFP_KERNEL);
+ if (!dev->dma_parms) {
+ ret = -ENOMEM;
+ goto err_config_cleanup;
+ }
+
+ /* TODO(djkurtz): fetch the mapping start/size from somewhere */
+ mapping = arm_iommu_create_mapping(&platform_bus_type, 0x10000000,
+ SZ_1G);
+ if (IS_ERR(mapping)) {
+ ret = PTR_ERR(mapping);
+ goto err_config_cleanup;
+ }
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ goto err_release_mapping;
+
+ dma_set_max_seg_size(dev, 0xffffffffu);
+
+ ret = arm_iommu_attach_device(dev, mapping);
+ if (ret)
+ goto err_release_mapping;
+
+ /* Try to bind all sub drivers. */
+ ret = component_bind_all(dev, drm_dev);
+ if (ret)
+ goto err_detach_device;
+
+ /* 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;
+
+ /*
+ * with vblank_disable_allowed = true, vblank interrupt will be disabled
+ * by drm timer once a current process gives up ownership of
+ * vblank event.(after drm_vblank_put function is called)
+ */
+ drm_dev->vblank_disable_allowed = true;
+
+ ret = drm_vblank_init(drm_dev, ROCKCHIP_MAX_CRTC);
+ if (ret)
+ goto err_kms_helper_poll_fini;
+
+ rockchip_drm_fbdev_init(drm_dev);
+
+ /* force connectors detection */
+ drm_helper_hpd_irq_event(drm_dev);
+
+ return 0;
+
+err_kms_helper_poll_fini:
+ drm_kms_helper_poll_fini(drm_dev);
+ component_unbind_all(dev, drm_dev);
+err_detach_device:
+ arm_iommu_detach_device(dev);
+err_release_mapping:
+ arm_iommu_release_mapping(dev->archdata.mapping);
+err_config_cleanup:
+ drm_mode_config_cleanup(drm_dev);
+ drm_dev->dev_private = NULL;
+ return ret;
+}
+
+static int rockchip_drm_unload(struct drm_device *drm_dev)
+{
+ struct device *dev = drm_dev->dev;
+
+ drm_kms_helper_poll_fini(drm_dev);
+ component_unbind_all(dev, drm_dev);
+ arm_iommu_detach_device(dev);
+ arm_iommu_release_mapping(dev->archdata.mapping);
+ drm_mode_config_cleanup(drm_dev);
+ drm_dev->dev_private = NULL;
+
+ return 0;
+}
+
+void rockchip_drm_lastclose(struct drm_device *dev)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+
+ if (priv->fb_helper)
+ drm_fb_helper_restore_fbdev_mode_unlocked(priv->fb_helper);
+}
+
+static const struct file_operations rockchip_drm_driver_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .mmap = rockchip_drm_gem_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .release = drm_release,
+};
+
+const struct vm_operations_struct rockchip_drm_vm_ops = {
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static struct drm_driver rockchip_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
+ .load = rockchip_drm_load,
+ .unload = rockchip_drm_unload,
+ .lastclose = rockchip_drm_lastclose,
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = rockchip_drm_crtc_enable_vblank,
+ .disable_vblank = rockchip_drm_crtc_disable_vblank,
+ .gem_vm_ops = &rockchip_drm_vm_ops,
+ .gem_free_object = 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_prime_mmap,
+ .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 int rockchip_drm_suspend(struct drm_device *dev, pm_message_t state)
+{
+ struct drm_connector *connector;
+
+ drm_modeset_lock_all(dev);
+ list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+ int old_dpms = connector->dpms;
+
+ if (connector->funcs->dpms)
+ connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
+
+ /* Set the old mode back to the connector for resume */
+ connector->dpms = old_dpms;
+ }
+ drm_modeset_unlock_all(dev);
+
+ return 0;
+}
+
+static int rockchip_drm_resume(struct drm_device *dev)
+{
+ struct drm_connector *connector;
+
+ drm_modeset_lock_all(dev);
+ list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+ if (connector->funcs->dpms)
+ connector->funcs->dpms(connector, connector->dpms);
+ }
+ drm_modeset_unlock_all(dev);
+
+ drm_helper_resume_force_mode(dev);
+
+ return 0;
+}
+
+static int rockchip_drm_sys_suspend(struct device *dev)
+{
+ struct drm_device *drm_dev = dev_get_drvdata(dev);
+ pm_message_t message;
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ message.event = PM_EVENT_SUSPEND;
+
+ return rockchip_drm_suspend(drm_dev, message);
+}
+
+static int rockchip_drm_sys_resume(struct device *dev)
+{
+ struct drm_device *drm_dev = dev_get_drvdata(dev);
+
+ if (!pm_runtime_suspended(dev))
+ return 0;
+
+ return rockchip_drm_resume(drm_dev);
+}
+#endif
+
+static const struct dev_pm_ops rockchip_drm_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
+ rockchip_drm_sys_resume)
+};
+
+int rockchip_drm_add_crtc(struct drm_device *drm, struct drm_crtc *crtc,
+ struct device_node *np)
+{
+ struct rockchip_drm_private *priv = drm->dev_private;
+ struct device_node *port;
+ int pipe;
+
+ if (priv->num_pipe >= ROCKCHIP_MAX_CRTC)
+ return -EINVAL;
+
+ port = of_get_child_by_name(np, "port");
+ if (!port) {
+ dev_err(drm->dev, "no port node found in %s\n",
+ np->full_name);
+ return -ENXIO;
+ }
+ pipe = priv->num_pipe++;
+ crtc->port = port;
+
+ priv->crtc[pipe] = crtc;
+
+ return pipe;
+}
+
+void rockchip_drm_remove_crtc(struct drm_device *drm, int pipe)
+{
+ struct rockchip_drm_private *priv = drm->dev_private;
+
+ priv->num_pipe--;
+ of_node_put(priv->crtc[pipe]->port);
+ priv->crtc[pipe] = NULL;
+}
+
+struct drm_crtc *rockchip_drm_find_crtc(struct drm_device *drm, int pipe)
+{
+ struct rockchip_drm_private *priv = drm->dev_private;
+
+ if (pipe < ROCKCHIP_MAX_CRTC && priv->crtc[pipe])
+ return priv->crtc[pipe];
+
+ return NULL;
+}
+
+/*
+ * @node: device tree node containing encoder input ports
+ * @encoder: drm_encoder
+ */
+int rockchip_drm_encoder_get_mux_id(struct device_node *node,
+ struct drm_encoder *encoder)
+{
+ struct device_node *ep = NULL;
+ struct drm_crtc *crtc = encoder->crtc;
+ struct of_endpoint endpoint;
+ struct device_node *port;
+ int ret;
+
+ if (!node || !crtc)
+ return -EINVAL;
+
+ do {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (!ep)
+ break;
+
+ port = of_graph_get_remote_port(ep);
+ of_node_put(port);
+ if (port == crtc->port) {
+ ret = of_graph_parse_endpoint(ep, &endpoint);
+ return ret ?: endpoint.id;
+ }
+ } while (ep);
+
+ return -EINVAL;
+}
+
+static int compare_of(struct device *dev, void *data)
+{
+ struct device_node *np = data;
+
+ return dev->of_node == np;
+}
+
+static void rockchip_add_endpoints(struct device *dev,
+ struct component_match **match,
+ struct device_node *port)
+{
+ struct device_node *ep, *remote;
+
+ for_each_child_of_node(port, ep) {
+ remote = of_graph_get_remote_port_parent(ep);
+ if (!remote || !of_device_is_available(remote)) {
+ of_node_put(remote);
+ continue;
+ } else if (!of_device_is_available(remote->parent)) {
+ dev_warn(dev, "parent device of %s is not available\n",
+ remote->full_name);
+ of_node_put(remote);
+ continue;
+ }
+
+ component_match_add(dev, match, compare_of, remote);
+ of_node_put(remote);
+ }
+}
+
+static int rockchip_drm_bind(struct device *dev)
+{
+ struct drm_device *drm;
+ int ret;
+
+ drm = drm_dev_alloc(&rockchip_drm_driver, dev);
+ if (!drm)
+ return -ENOMEM;
+
+ ret = drm_dev_set_unique(drm, "%s", dev_name(dev));
+ if (ret)
+ goto err_free;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret)
+ goto err_free;
+
+ dev_set_drvdata(dev, drm);
+
+ return 0;
+
+err_free:
+ drm_dev_unref(drm);
+ return ret;
+}
+
+static void rockchip_drm_unbind(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ drm_dev_unregister(drm);
+ drm_dev_unref(drm);
+}
+
+static const struct component_master_ops rockchip_drm_ops = {
+ .bind = rockchip_drm_bind,
+ .unbind = rockchip_drm_unbind,
+};
+
+static int rockchip_drm_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match = NULL;
+ struct device_node *np = dev->of_node;
+ struct device_node *port;
+ int i;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+ /*
+ * Bind the crtc ports first, so that
+ * drm_of_find_possible_crtcs called from encoder .bind callbacks
+ * works as expected.
+ */
+ for (i = 0;; i++) {
+ port = of_parse_phandle(np, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ component_match_add(dev, &match, compare_of, port->parent);
+ of_node_put(port);
+ }
+
+ if (i == 0) {
+ dev_err(dev, "missing 'ports' property\n");
+ return -ENODEV;
+ }
+ /*
+ * For each bound crtc, bind the encoders attached to its
+ * remote endpoint.
+ */
+ for (i = 0;; i++) {
+ port = of_parse_phandle(np, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ rockchip_add_endpoints(dev, &match, port);
+ of_node_put(port);
+ }
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ 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 = {
+ .owner = THIS_MODULE,
+ .name = "rockchip-drm",
+ .of_match_table = rockchip_drm_dt_ids,
+ .pm = &rockchip_drm_pm_ops,
+ },
+};
+
+module_platform_driver(rockchip_drm_platform_driver);
+
+MODULE_AUTHOR("Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>");
+MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
new file mode 100644
index 0000000..98094ecf
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * based on exynos_drm_drv.h
+ *
+ * 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.
+ */
+
+#ifndef _ROCKCHIP_DRM_DRV_H
+#define _ROCKCHIP_DRM_DRV_H
+
+#include <linux/module.h>
+#include <linux/component.h>
+
+#define ROCKCHIP_MAX_FB_BUFFER 3
+#define ROCKCHIP_MAX_CONNECTOR 2
+#define ROCKCHIP_MAX_CRTC 2
+
+struct drm_device;
+struct drm_connector;
+
+/*
+ * Rockchip drm private structure.
+ *
+ * @crtc: array of enabled CRTCs, used to map from "pipe" to drm_crtc.
+ * @num_pipe: number of pipes for this device.
+ */
+struct rockchip_drm_private {
+ struct drm_fb_helper *fb_helper;
+ /*
+ * created crtc object would be contained at this array and
+ * this array is used to be aware of which crtc did it request vblank.
+ */
+ struct drm_crtc *crtc[ROCKCHIP_MAX_CRTC];
+
+ unsigned int num_pipe;
+};
+
+int rockchip_drm_add_crtc(struct drm_device *drm, struct drm_crtc *crtc,
+ struct device_node *port);
+void rockchip_drm_remove_crtc(struct drm_device *drm, int pipe);
+struct drm_crtc *rockchip_drm_find_crtc(struct drm_device *drm, int pipe);
+int rockchip_drm_encoder_get_mux_id(struct device_node *node,
+ struct drm_encoder *encoder);
+int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type);
+int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe);
+void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe);
+int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
+ struct device *dev);
+void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
+ struct device *dev);
+
+#endif /* _ROCKCHIP_DRM_DRV_H_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
new file mode 100644
index 0000000..482f7b8
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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 <linux/kernel.h>
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+
+#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
+
+struct rockchip_drm_fb {
+ struct drm_framebuffer fb;
+ struct drm_gem_object *obj[ROCKCHIP_MAX_FB_BUFFER];
+};
+
+struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane)
+{
+ struct rockchip_drm_fb *rk_fb = to_rockchip_fb(fb);
+
+ if (plane >= ROCKCHIP_MAX_FB_BUFFER)
+ return NULL;
+
+ return rk_fb->obj[plane];
+}
+
+static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
+ struct drm_gem_object *obj;
+ int i;
+
+ for (i = 0; i < ROCKCHIP_MAX_FB_BUFFER; i++) {
+ obj = rockchip_fb->obj[i];
+ if (obj)
+ drm_gem_object_unreference_unlocked(obj);
+ }
+
+ drm_framebuffer_cleanup(fb);
+ kfree(rockchip_fb);
+}
+
+static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned int *handle)
+{
+ struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
+
+ return drm_gem_handle_create(file_priv,
+ rockchip_fb->obj[0], handle);
+}
+
+static struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
+ .destroy = rockchip_drm_fb_destroy,
+ .create_handle = rockchip_drm_fb_create_handle,
+};
+
+static struct rockchip_drm_fb *
+rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object **obj, unsigned int num_planes)
+{
+ struct rockchip_drm_fb *rockchip_fb;
+ int ret;
+ int i;
+
+ rockchip_fb = kzalloc(sizeof(*rockchip_fb), GFP_KERNEL);
+ if (!rockchip_fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(&rockchip_fb->fb, mode_cmd);
+
+ for (i = 0; i < num_planes; i++)
+ rockchip_fb->obj[i] = obj[i];
+
+ ret = drm_framebuffer_init(dev, &rockchip_fb->fb,
+ &rockchip_drm_fb_funcs);
+ if (ret) {
+ dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
+ ret);
+ kfree(rockchip_fb);
+ return ERR_PTR(ret);
+ }
+
+ return rockchip_fb;
+}
+
+static struct drm_framebuffer *
+rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct rockchip_drm_fb *rockchip_fb;
+ struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER];
+ struct drm_gem_object *obj;
+ unsigned int hsub;
+ unsigned int vsub;
+ int num_planes;
+ int ret;
+ int i;
+
+ hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
+ vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
+ num_planes = min(drm_format_num_planes(mode_cmd->pixel_format),
+ ROCKCHIP_MAX_FB_BUFFER);
+
+ for (i = 0; i < num_planes; i++) {
+ unsigned int width = mode_cmd->width / (i ? hsub : 1);
+ unsigned int height = mode_cmd->height / (i ? vsub : 1);
+ unsigned int min_size;
+
+ obj = drm_gem_object_lookup(dev, file_priv,
+ mode_cmd->handles[i]);
+ if (!obj) {
+ dev_err(dev->dev, "Failed to lookup GEM object\n");
+ ret = -ENXIO;
+ goto err_gem_object_unreference;
+ }
+
+ min_size = (height - 1) * mode_cmd->pitches[i] +
+ mode_cmd->offsets[i] +
+ width * drm_format_plane_cpp(mode_cmd->pixel_format, i);
+
+ if (obj->size < min_size) {
+ drm_gem_object_unreference_unlocked(obj);
+ ret = -EINVAL;
+ goto err_gem_object_unreference;
+ }
+ objs[i] = obj;
+ }
+
+ rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, objs, i);
+ if (IS_ERR(rockchip_fb)) {
+ ret = PTR_ERR(rockchip_fb);
+ goto err_gem_object_unreference;
+ }
+
+ return &rockchip_fb->fb;
+
+err_gem_object_unreference:
+ for (i--; i >= 0; i--)
+ drm_gem_object_unreference_unlocked(objs[i]);
+ return ERR_PTR(ret);
+}
+
+static void rockchip_drm_output_poll_changed(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *fb_helper = private->fb_helper;
+
+ if (fb_helper)
+ drm_fb_helper_hotplug_event(fb_helper);
+}
+
+static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
+ .fb_create = rockchip_user_fb_create,
+ .output_poll_changed = rockchip_drm_output_poll_changed,
+};
+
+struct drm_framebuffer *
+rockchip_drm_framebuffer_init(struct drm_device *dev,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj)
+{
+ struct rockchip_drm_fb *rockchip_fb;
+
+ rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1);
+ if (IS_ERR(rockchip_fb))
+ return NULL;
+
+ return &rockchip_fb->fb;
+}
+
+void rockchip_drm_mode_config_init(struct drm_device *dev)
+{
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+
+ /*
+ * set max width and height as default value(4096x4096).
+ * this value would be used to check framebuffer size limitation
+ * at drm_mode_addfb().
+ */
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 4096;
+
+ dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
new file mode 100644
index 0000000..09574d4
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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.
+ */
+
+#ifndef _ROCKCHIP_DRM_FB_H
+#define _ROCKCHIP_DRM_FB_H
+
+struct drm_framebuffer *
+rockchip_drm_framebuffer_init(struct drm_device *dev,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj);
+void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
+
+void rockchip_drm_mode_config_init(struct drm_device *dev);
+
+struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane);
+#endif /* _ROCKCHIP_DRM_FB_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
new file mode 100644
index 0000000..ae563f5
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+
+#define PREFERRED_BPP 32
+#define to_rockchip_fbdev(x) container_of(x, struct rockchip_fbdev, helper)
+
+struct rockchip_fbdev {
+ struct drm_fb_helper helper;
+ struct drm_gem_object *bo;
+};
+
+static int rockchip_fbdev_mmap(struct fb_info *info,
+ struct vm_area_struct *vma)
+{
+ struct drm_fb_helper *helper = info->par;
+ struct rockchip_fbdev *fbdev = to_rockchip_fbdev(helper);
+
+ return rockchip_gem_mmap(fbdev->bo, vma);
+}
+
+static struct fb_ops rockchip_drm_fbdev_ops = {
+ .owner = THIS_MODULE,
+ .fb_mmap = rockchip_fbdev_mmap,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_setcmap = drm_fb_helper_setcmap,
+};
+
+static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct rockchip_fbdev *fbdev = to_rockchip_fbdev(helper);
+ struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+ struct drm_device *dev = helper->dev;
+ struct rockchip_gem_object *rk_obj;
+ struct drm_framebuffer *fb;
+ unsigned int bytes_per_pixel;
+ unsigned long offset;
+ struct fb_info *fbi;
+ size_t size;
+ int ret;
+
+ bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ size = mode_cmd.pitches[0] * mode_cmd.height;
+
+ rk_obj = rockchip_gem_create_object(dev, size);
+ if (IS_ERR(rk_obj))
+ return -ENOMEM;
+
+ fbdev->bo = &rk_obj->base;
+
+ fbi = framebuffer_alloc(0, dev->dev);
+ if (!fbi) {
+ dev_err(dev->dev, "Failed to allocate framebuffer info.\n");
+ ret = -ENOMEM;
+ goto err_rockchip_gem_free_object;
+ }
+
+ helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd, fbdev->bo);
+ if (IS_ERR(helper->fb)) {
+ dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
+ ret = PTR_ERR(helper->fb);
+ goto err_framebuffer_release;
+ }
+
+ helper->fbdev = fbi;
+
+ fbi->par = helper;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->fbops = &rockchip_drm_fbdev_ops;
+
+ ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
+ if (ret) {
+ dev_err(dev->dev, "Failed to allocate color map.\n");
+ goto err_drm_framebuffer_unref;
+ }
+
+ fb = helper->fb;
+ drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
+ drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
+
+ offset = fbi->var.xoffset * bytes_per_pixel;
+ offset += fbi->var.yoffset * fb->pitches[0];
+
+ dev->mode_config.fb_base = 0;
+ fbi->screen_base = rk_obj->kvaddr + offset;
+ fbi->screen_size = rk_obj->base.size;
+ fbi->fix.smem_len = rk_obj->base.size;
+
+ DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%d\n",
+ fb->width, fb->height, fb->depth, rk_obj->kvaddr,
+ offset, size);
+ return 0;
+
+err_drm_framebuffer_unref:
+ drm_framebuffer_unreference(helper->fb);
+err_framebuffer_release:
+ framebuffer_release(fbi);
+err_rockchip_gem_free_object:
+ rockchip_gem_free_object(&rk_obj->base);
+ return ret;
+}
+
+static struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
+ .fb_probe = rockchip_drm_fbdev_create,
+};
+
+int rockchip_drm_fbdev_init(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct rockchip_fbdev *fbdev;
+ struct drm_fb_helper *helper;
+ unsigned int num_crtc;
+ int ret;
+
+ if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
+ return -EINVAL;
+
+ if (private->fb_helper) {
+ DRM_ERROR("no allow to reinit fbdev\n");
+ return -EINVAL;
+ }
+
+ num_crtc = dev->mode_config.num_crtc;
+
+ fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
+ if (!fbdev)
+ return -ENOMEM;
+
+ fbdev->helper.funcs = &rockchip_drm_fb_helper_funcs;
+ helper = &fbdev->helper;
+
+ ret = drm_fb_helper_init(dev, helper, num_crtc, ROCKCHIP_MAX_CONNECTOR);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
+ goto err_free;
+ }
+
+ ret = drm_fb_helper_single_add_all_connectors(helper);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to add connectors.\n");
+ goto err_drm_fb_helper_fini;
+ }
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ drm_helper_disable_unused_functions(dev);
+
+ ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to set initial hw configuration.\n");
+ goto err_drm_fb_helper_fini;
+ }
+
+ private->fb_helper = helper;
+
+ return 0;
+
+err_drm_fb_helper_fini:
+ drm_fb_helper_fini(helper);
+err_free:
+ kfree(fbdev);
+ return ret;
+}
+
+void rockchip_drm_fbdev_fini(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *helper;
+ struct rockchip_fbdev *fbdev;
+
+ if (!private || !private->fb_helper)
+ return;
+
+ helper = private->fb_helper;
+ fbdev = to_rockchip_fbdev(helper);
+
+ if (helper->fbdev) {
+ struct fb_info *info;
+ int ret;
+
+ info = helper->fbdev;
+ ret = unregister_framebuffer(info);
+ if (ret < 0)
+ DRM_DEBUG_KMS("failed unregister_framebuffer()\n");
+
+ if (info->cmap.len)
+ fb_dealloc_cmap(&info->cmap);
+
+ framebuffer_release(info);
+ }
+
+ if (helper->fb)
+ drm_framebuffer_unreference(helper->fb);
+
+ drm_fb_helper_fini(helper);
+ kfree(fbdev);
+ private->fb_helper = NULL;
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
new file mode 100644
index 0000000..5edcf6a
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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.
+ */
+
+#ifndef _ROCKCHIP_DRM_FBDEV_H
+#define _ROCKCHIP_DRM_FBDEV_H
+
+int rockchip_drm_fbdev_init(struct drm_device *dev);
+
+#endif /* _ROCKCHIP_DRM_FBDEV_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
new file mode 100644
index 0000000..5563dff
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_vma_manager.h>
+
+#include <linux/anon_inodes.h>
+#include <linux/dma-attrs.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+
+static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+
+ init_dma_attrs(&rk_obj->dma_attrs);
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &rk_obj->dma_attrs);
+
+ /* TODO(djkurtz): Use DMA_ATTR_NO_KERNEL_MAPPING except for fbdev */
+ rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
+ &rk_obj->dma_addr, GFP_KERNEL,
+ &rk_obj->dma_attrs);
+ if (IS_ERR(rk_obj->kvaddr)) {
+ int ret = PTR_ERR(rk_obj->kvaddr);
+
+ DRM_ERROR("failed to allocate %#x byte dma buffer, %d",
+ obj->size, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rockchip_gem_free_buf(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+
+ dma_free_attrs(drm->dev, obj->size, rk_obj->kvaddr, rk_obj->dma_addr,
+ &rk_obj->dma_attrs);
+}
+
+/* drm driver mmap file operations */
+int rockchip_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_file *priv = filp->private_data;
+ struct drm_device *dev = priv->minor->dev;
+ struct drm_gem_object *obj;
+ struct drm_vma_offset_node *node;
+ int ret;
+
+ if (drm_device_is_unplugged(dev))
+ return -ENODEV;
+
+ mutex_lock(&dev->struct_mutex);
+
+ node = drm_vma_offset_exact_lookup(dev->vma_offset_manager,
+ vma->vm_pgoff,
+ vma_pages(vma));
+ if (!node) {
+ mutex_unlock(&dev->struct_mutex);
+ DRM_ERROR("failed to find vma node.\n");
+ return -EINVAL;
+ } else if (!drm_vma_node_is_allowed(node, filp)) {
+ mutex_unlock(&dev->struct_mutex);
+ return -EACCES;
+ }
+
+ obj = container_of(node, struct drm_gem_object, vma_node);
+ ret = rockchip_gem_mmap(obj, vma);
+
+ mutex_unlock(&dev->struct_mutex);
+
+ return ret;
+}
+
+int rockchip_drm_gem_mmap_buffer(struct file *filp,
+ struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj = filp->private_data;
+
+ return rockchip_gem_mmap(obj, vma);
+}
+
+static const struct file_operations rockchip_drm_gem_fops = {
+ .mmap = rockchip_drm_gem_mmap_buffer,
+};
+
+struct rockchip_gem_object *
+ rockchip_gem_create_object(struct drm_device *drm, unsigned int size)
+{
+ struct rockchip_gem_object *rk_obj;
+ struct drm_gem_object *obj;
+ struct file *filp;
+ int ret;
+
+ size = round_up(size, PAGE_SIZE);
+
+ rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
+ if (!rk_obj)
+ return ERR_PTR(-ENOMEM);
+
+ obj = &rk_obj->base;
+
+ drm_gem_private_object_init(drm, obj, size);
+
+ filp = anon_inode_getfile("rockchip_gem", &rockchip_drm_gem_fops,
+ obj, 0);
+ if (IS_ERR(filp)) {
+ DRM_ERROR("failed to create anon file object.\n");
+ ret = PTR_ERR(filp);
+ goto err_free_rk_obj;
+ }
+ filp->f_mode = FMODE_READ | FMODE_WRITE;
+ obj->filp = filp;
+
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret)
+ goto err_free_obj;
+
+ ret = rockchip_gem_alloc_buf(rk_obj);
+ if (ret)
+ goto err_free_mmap_offset;
+
+ return rk_obj;
+
+err_free_mmap_offset:
+ drm_gem_free_mmap_offset(obj);
+err_free_obj:
+ drm_gem_object_release(obj);
+err_free_rk_obj:
+ kfree(rk_obj);
+ return ERR_PTR(ret);
+}
+
+/*
+ * rockchip_gem_free_object - (struct drm_driver)->gem_free_object callback
+ * function
+ */
+void rockchip_gem_free_object(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj;
+
+ drm_gem_free_mmap_offset(obj);
+
+ rk_obj = to_rockchip_obj(obj);
+
+ rockchip_gem_free_buf(rk_obj);
+
+ drm_gem_object_release(obj);
+
+ kfree(rk_obj);
+}
+
+int rockchip_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ struct drm_device *drm = obj->dev;
+ unsigned long vm_size;
+
+ vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+ vm_size = vma->vm_end - vma->vm_start;
+
+ if (vm_size > obj->size)
+ return -EINVAL;
+
+ return dma_mmap_attrs(drm->dev, vma, rk_obj->kvaddr, rk_obj->dma_addr,
+ obj->size, &rk_obj->dma_attrs);
+}
+
+/*
+ * rockchip_gem_create_with_handle - allocate an object with the given
+ * size and create a gem handle on it
+ *
+ * returns a struct rockchip_gem_object* on success or ERR_PTR values
+ * on failure.
+ */
+static struct rockchip_gem_object *
+rockchip_gem_create_with_handle(struct drm_file *file_priv,
+ struct drm_device *drm, unsigned int size,
+ unsigned int *handle)
+{
+ struct rockchip_gem_object *rk_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ rk_obj = rockchip_gem_create_object(drm, size);
+ if (IS_ERR(rk_obj))
+ return NULL;
+
+ obj = &rk_obj->base;
+
+ /*
+ * allocate a id of idr table where the obj is registered
+ * and handle has the id what user can see.
+ */
+ ret = drm_gem_handle_create(file_priv, obj, handle);
+ if (ret)
+ goto err_handle_create;
+
+ /* drop reference from allocate - handle holds it now. */
+ drm_gem_object_unreference_unlocked(obj);
+
+ return rk_obj;
+
+err_handle_create:
+ rockchip_gem_free_object(obj);
+
+ return ERR_PTR(ret);
+}
+
+int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle,
+ uint64_t *offset)
+{
+ struct drm_gem_object *obj;
+ int ret = 0;
+
+ mutex_lock(&dev->struct_mutex);
+
+ /*
+ * get offset of memory allocated for drm framebuffer.
+ * - this callback would be called by user application
+ * with DRM_IOCTL_MODE_MAP_DUMB command.
+ */
+
+ obj = drm_gem_object_lookup(dev, file_priv, handle);
+ if (!obj) {
+ DRM_ERROR("failed to lookup gem object.\n");
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret)
+ goto out;
+
+ *offset = drm_vma_node_offset_addr(&obj->vma_node);
+ DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset);
+
+out:
+ drm_gem_object_unreference(obj);
+unlock:
+ mutex_unlock(&dev->struct_mutex);
+ return ret;
+}
+
+/*
+ * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
+ * function
+ *
+ * This aligns the pitch and size arguments to the minimum required. wrap
+ * this into your own function if you need bigger alignment.
+ */
+int rockchip_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct rockchip_gem_object *rk_obj;
+ int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+
+ /*
+ * align to 64 bytes since Mali requires it.
+ */
+ min_pitch = ((min_pitch + 63) / 64) * 64;
+
+ if (args->pitch < min_pitch)
+ args->pitch = min_pitch;
+
+ if (args->size < args->pitch * args->height)
+ args->size = args->pitch * args->height;
+
+ rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
+ &args->handle);
+
+ return PTR_ERR_OR_ZERO(rk_obj);
+}
+
+/*
+ * Allocate a sg_table for this GEM object.
+ * Note: Both the table's contents, and the sg_table itself must be freed by
+ * the caller.
+ * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
+ */
+struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ struct drm_device *drm = obj->dev;
+ struct sg_table *sgt = NULL;
+ int ret;
+
+ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+ if (!sgt)
+ return ERR_PTR(-ENOMEM);
+
+ ret = dma_get_sgtable_attrs(drm->dev, sgt, rk_obj->kvaddr,
+ rk_obj->dma_addr, obj->size,
+ &rk_obj->dma_attrs);
+ if (ret) {
+ DRM_ERROR("failed to allocate sgt, %d\n", ret);
+ kfree(sgt);
+ return ERR_PTR(ret);
+ }
+
+ return sgt;
+}
+
+void *rockchip_gem_prime_vmap(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+
+ return rk_obj->kvaddr;
+}
+
+void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
+{
+ /* Nothing to do */
+}
+
+int rockchip_gem_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ struct drm_device *dev = obj->dev;
+ int ret;
+
+ mutex_lock(&dev->struct_mutex);
+ ret = drm_gem_mmap_obj(obj, obj->size, vma);
+ mutex_unlock(&dev->struct_mutex);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.h b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
new file mode 100644
index 0000000..e37b921
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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.
+ */
+
+#ifndef _ROCKCHIP_DRM_GEM_H
+#define _ROCKCHIP_DRM_GEM_H
+
+#define to_rockchip_obj(x) container_of(x, struct rockchip_gem_object, base)
+
+struct rockchip_gem_object {
+ struct drm_gem_object base;
+ unsigned int flags;
+
+ void *kvaddr;
+ dma_addr_t dma_addr;
+ struct dma_attrs dma_attrs;
+};
+
+struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj);
+struct drm_gem_object *
+rockchip_gem_prime_import_sg_table(struct drm_device *dev, size_t size,
+ struct sg_table *sgt);
+void *rockchip_gem_prime_vmap(struct drm_gem_object *obj);
+void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
+int rockchip_gem_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma);
+
+/* drm driver mmap file operations */
+int rockchip_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* mmap a gem object to userspace. */
+int rockchip_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
+
+struct rockchip_gem_object *
+ rockchip_gem_create_object(struct drm_device *drm, unsigned int size);
+
+void rockchip_gem_free_object(struct drm_gem_object *obj);
+
+int rockchip_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle,
+ uint64_t *offset);
+#endif /* _ROCKCHIP_DRM_GEM_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
new file mode 100644
index 0000000..fe9d2b0
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -0,0 +1,1422 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/component.h>
+
+#include <linux/reset.h>
+#include <linux/iommu.h>
+#include <linux/delay.h>
+
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_fbdev.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_vop.h"
+
+#define VOP_DEFAULT_FRAMERATE 60
+#define VOP_MAX_WIN_SUPPORT 5
+#define VOP_DEFAULT_PRIMARY 0
+#define VOP_DEFAULT_CURSOR 1
+#define VOP_REG(off, _mask, s) \
+ {.offset = off, \
+ .mask = _mask, \
+ .shift = s,}
+
+#define __REG_SET(x, off, mask, shift, v) \
+ vop_mask_write(x, off, (mask) << shift, (v) << shift)
+
+#define REG_SET(x, base, reg, v) \
+ __REG_SET(x, base + reg.offset, reg.mask, reg.shift, v)
+
+#define VOP_WIN_SET(x, win, name, v) \
+ REG_SET(x, win->base, win->phy->name, v)
+#define VOP_CTRL_SET(x, name, v) \
+ REG_SET(x, 0, (x)->data->ctrl->name, v)
+
+#define VOP_WIN_GET_YRGBADDR(ctx, win) \
+ vop_readl(ctx, win->base + win->phy->yrgb_mst.offset)
+
+#define to_vop_ctx(x) container_of(x, struct vop_context, crtc)
+#define to_rockchip_plane(x) container_of(x, struct rockchip_plane, base)
+
+struct rockchip_plane {
+ int id;
+ struct drm_plane base;
+ const struct vop_win *win;
+
+ uint32_t pending_yrgb_mst;
+ struct drm_framebuffer *front_fb;
+ struct drm_framebuffer *pending_fb;
+ bool enabled;
+};
+
+struct vop_context {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ struct drm_crtc crtc;
+ struct drm_pending_vblank_event *event;
+ struct vop_driver *drv;
+ unsigned int dpms;
+ unsigned int win_mask;
+ wait_queue_head_t wait_vsync_queue;
+ atomic_t wait_vsync_event;
+
+ struct workqueue_struct *vsync_wq;
+ struct work_struct vsync_work;
+
+ /* mutex vsync_ work */
+ struct mutex vsync_mutex;
+ bool vsync_work_pending;
+
+ struct vop_driver_data *data;
+
+ uint32_t *regsbak;
+ void __iomem *regs;
+
+ /* physical map length of vop register */
+ uint32_t len;
+
+ /* one time only one process allowed to config the register */
+ spinlock_t reg_lock;
+ /* lock vop irq reg */
+ spinlock_t irq_lock;
+
+ unsigned int irq;
+
+ /* vop AHP clk */
+ struct clk *hclk;
+ /* vop dclk */
+ struct clk *dclk;
+ /* vop share memory frequency */
+ struct clk *aclk;
+
+ /* vop ahb reset */
+ struct reset_control *ahb_rst;
+ /* vop dclk reset */
+ struct reset_control *dclk_rst;
+ uint32_t pixclock;
+
+ int pipe;
+ bool clk_on;
+};
+
+enum vop_data_format {
+ VOP_FMT_ARGB8888 = 0,
+ VOP_FMT_RGB888,
+ VOP_FMT_RGB565,
+ VOP_FMT_YUV420SP = 4,
+ VOP_FMT_YUV422SP,
+ VOP_FMT_YUV444SP,
+};
+
+struct vop_reg_data {
+ uint32_t offset;
+ uint32_t value;
+};
+
+struct vop_reg {
+ uint32_t offset;
+ uint32_t shift;
+ uint32_t mask;
+};
+
+struct vop_ctrl {
+ struct vop_reg standby;
+ struct vop_reg data_blank;
+ struct vop_reg gate_en;
+ struct vop_reg mmu_en;
+ struct vop_reg rgb_en;
+ struct vop_reg edp_en;
+ struct vop_reg hdmi_en;
+ struct vop_reg mipi_en;
+ struct vop_reg out_mode;
+ struct vop_reg dither_down;
+ struct vop_reg dither_up;
+ struct vop_reg pin_pol;
+
+ struct vop_reg htotal_pw;
+ struct vop_reg hact_st_end;
+ struct vop_reg vtotal_pw;
+ struct vop_reg vact_st_end;
+ struct vop_reg hpost_st_end;
+ struct vop_reg vpost_st_end;
+};
+
+struct vop_win_phy {
+ const uint32_t *data_formats;
+ uint32_t nformats;
+
+ struct vop_reg enable;
+ struct vop_reg format;
+ struct vop_reg act_info;
+ struct vop_reg dsp_info;
+ struct vop_reg dsp_st;
+ struct vop_reg yrgb_mst;
+ struct vop_reg uv_mst;
+ struct vop_reg yrgb_vir;
+ struct vop_reg uv_vir;
+
+ struct vop_reg dst_alpha_ctl;
+ struct vop_reg src_alpha_ctl;
+};
+
+struct vop_win {
+ uint32_t base;
+ const struct vop_win_phy *phy;
+};
+
+struct vop_driver_data {
+ const void *init_table;
+ int table_size;
+ const struct vop_ctrl *ctrl;
+ const struct vop_win *win[VOP_MAX_WIN_SUPPORT];
+};
+
+static const uint32_t formats_01[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+};
+
+static const uint32_t formats_234[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_RGB565,
+};
+
+static const struct vop_win_phy win01_data = {
+ .data_formats = formats_01,
+ .nformats = ARRAY_SIZE(formats_01),
+ .enable = VOP_REG(WIN0_CTRL0, 0x1, 0),
+ .format = VOP_REG(WIN0_CTRL0, 0x7, 1),
+ .act_info = VOP_REG(WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(WIN0_DSP_INFO, 0x1fff1fff, 0),
+ .dsp_st = VOP_REG(WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(WIN0_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(WIN0_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(WIN0_VIR, 0x3fff, 0),
+ .uv_vir = VOP_REG(WIN0_VIR, 0x3fff, 16),
+ .src_alpha_ctl = VOP_REG(WIN0_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(WIN0_DST_ALPHA_CTRL, 0xff, 0),
+};
+
+static const struct vop_win_phy win23_data = {
+ .data_formats = formats_234,
+ .nformats = ARRAY_SIZE(formats_234),
+ .enable = VOP_REG(WIN2_CTRL0, 0x1, 0),
+ .format = VOP_REG(WIN2_CTRL0, 0x7, 1),
+ .dsp_info = VOP_REG(WIN2_DSP_INFO0, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(WIN2_DSP_ST0, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(WIN2_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(WIN2_VIR0_1, 0x1fff, 0),
+ .src_alpha_ctl = VOP_REG(WIN2_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(WIN2_DST_ALPHA_CTRL, 0xff, 0),
+};
+
+static const struct vop_win_phy cursor_data = {
+ .data_formats = formats_234,
+ .nformats = ARRAY_SIZE(formats_234),
+ .enable = VOP_REG(HWC_CTRL0, 0x1, 0),
+ .format = VOP_REG(HWC_CTRL0, 0x7, 1),
+ .dsp_st = VOP_REG(HWC_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(HWC_MST, 0xffffffff, 0),
+};
+
+static const struct vop_win win0 = {
+ .base = 0,
+ .phy = &win01_data,
+};
+
+static const struct vop_win win1 = {
+ .base = 0x40,
+ .phy = &win01_data,
+};
+
+static const struct vop_win win2 = {
+ .base = 0,
+ .phy = &win23_data,
+};
+
+static const struct vop_win win3 = {
+ .base = 0x50,
+ .phy = &win23_data,
+};
+
+static const struct vop_win win_cursor = {
+ .base = 0,
+ .phy = &cursor_data,
+};
+
+static const struct vop_ctrl ctrl_data = {
+ .standby = VOP_REG(SYS_CTRL, 0x1, 22),
+ .gate_en = VOP_REG(SYS_CTRL, 0x1, 23),
+ .mmu_en = VOP_REG(SYS_CTRL, 0x1, 20),
+ .rgb_en = VOP_REG(SYS_CTRL, 0x1, 12),
+ .hdmi_en = VOP_REG(SYS_CTRL, 0x1, 13),
+ .edp_en = VOP_REG(SYS_CTRL, 0x1, 14),
+ .mipi_en = VOP_REG(SYS_CTRL, 0x1, 15),
+ .dither_down = VOP_REG(DSP_CTRL1, 0xf, 1),
+ .dither_up = VOP_REG(DSP_CTRL1, 0x1, 6),
+ .data_blank = VOP_REG(DSP_CTRL0, 0x1, 19),
+ .out_mode = VOP_REG(DSP_CTRL0, 0xf, 0),
+ .pin_pol = VOP_REG(DSP_CTRL0, 0xf, 4),
+ .htotal_pw = VOP_REG(DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
+ .hact_st_end = VOP_REG(DSP_HACT_ST_END, 0x1fff1fff, 0),
+ .vtotal_pw = VOP_REG(DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
+ .vact_st_end = VOP_REG(DSP_VACT_ST_END, 0x1fff1fff, 0),
+ .hpost_st_end = VOP_REG(POST_DSP_HACT_INFO, 0x1fff1fff, 0),
+ .vpost_st_end = VOP_REG(POST_DSP_VACT_INFO, 0x1fff1fff, 0),
+};
+
+static const struct vop_reg_data vop_init_reg_table[] = {
+ {SYS_CTRL, 0x00801000},
+ {DSP_CTRL0, 0x00000000},
+ {WIN0_CTRL0, 0x00000080},
+ {WIN1_CTRL0, 0x00000080},
+};
+
+static const struct vop_driver_data rockchip_rk3288_vop = {
+ .init_table = vop_init_reg_table,
+ .table_size = ARRAY_SIZE(vop_init_reg_table),
+ .ctrl = &ctrl_data,
+ .win[0] = &win0,
+ .win[1] = &win1,
+ .win[2] = &win2,
+ .win[3] = &win3,
+ .win[4] = &win_cursor,
+};
+
+static const struct of_device_id vop_driver_dt_match[] = {
+ { .compatible = "rockchip,rk3288-vop",
+ .data = (void *)&rockchip_rk3288_vop },
+ {},
+};
+
+static inline void vop_writel(struct vop_context *ctx,
+ uint32_t offset, uint32_t v)
+{
+ writel(v, ctx->regs + offset);
+ ctx->regsbak[offset >> 2] = v;
+}
+
+static inline uint32_t vop_readl(struct vop_context *ctx, uint32_t offset)
+{
+ return readl(ctx->regs + offset);
+}
+
+static inline void vop_cfg_done(struct vop_context *ctx)
+{
+ writel(0x01, ctx->regs + REG_CFG_DONE);
+}
+
+static inline void vop_mask_write(struct vop_context *ctx,
+ uint32_t offset, uint32_t mask, uint32_t v)
+{
+ if (mask) {
+ uint32_t cached_val = ctx->regsbak[offset >> 2];
+
+ cached_val = (cached_val & ~mask) | v;
+ writel(cached_val, ctx->regs + offset);
+ ctx->regsbak[offset >> 2] = cached_val;
+ }
+}
+
+static inline struct vop_driver_data *vop_get_driver_data(struct device *dev)
+{
+ const struct of_device_id *of_id =
+ of_match_device(vop_driver_dt_match, dev);
+
+ return (struct vop_driver_data *)of_id->data;
+}
+
+static enum vop_data_format vop_convert_format(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ return VOP_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ return VOP_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ return VOP_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP_FMT_YUV420SP;
+ case DRM_FORMAT_NV16:
+ return VOP_FMT_YUV422SP;
+ case DRM_FORMAT_NV24:
+ return VOP_FMT_YUV444SP;
+ default:
+ DRM_ERROR("unsupport format[%08x]\n", format);
+ return -EINVAL;
+ }
+}
+
+static bool is_alpha_support(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_ARGB8888:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* TODO(djkurtz): move generic 'setup slave rk_iommu' code somewhere common */
+static int vop_iommu_init(struct vop_context *ctx)
+{
+ struct device *dev = ctx->dev;
+ struct device_node *np = dev->of_node;
+ struct platform_device *pd;
+ int count;
+ int ret;
+ struct of_phandle_args args;
+
+ /* Each VOP must have exactly one iommu node, with no args */
+ count = of_count_phandle_with_args(np, "iommus", "#iommu-cells");
+ if (count != 1) {
+ dev_err(dev, "of_count_phandle_with_args(%s) => %d\n",
+ np->full_name, count);
+ return -EINVAL;
+ }
+
+ ret = of_parse_phandle_with_args(np, "iommus", "#iommu-cells", 0,
+ &args);
+ if (ret) {
+ dev_err(dev, "of_parse_phandle_with_args(%s) => %d\n",
+ np->full_name, ret);
+ return ret;
+ }
+ if (args.args_count != 0) {
+ dev_err(dev, "incorrect number of iommu params found for %s (found %d, expected 0)\n",
+ args.np->full_name, args.args_count);
+ return -EINVAL;
+ }
+
+ pd = of_find_device_by_node(args.np);
+ of_node_put(args.np);
+ if (!pd) {
+ dev_err(dev, "iommu %s not found\n", args.np->full_name);
+ return -EPROBE_DEFER;
+ }
+
+ /* TODO(djkurtz): handle multiple slave iommus for a single master */
+ dev->archdata.iommu = &pd->dev;
+
+ ret = rockchip_drm_dma_attach_device(ctx->drm_dev, dev);
+ if (ret) {
+ dev_err(dev, "failed to attach to drm dma mapping, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vop_iommu_fini(struct vop_context *ctx)
+{
+ rockchip_drm_dma_detach_device(ctx->drm_dev, ctx->dev);
+}
+
+static int vop_clk_enable(struct vop_context *ctx)
+{
+ int ret;
+
+ if (!ctx->clk_on) {
+ ret = clk_enable(ctx->hclk);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable hclk\n");
+ return ret;
+ }
+
+ ret = clk_enable(ctx->dclk);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable dclk\n");
+ goto err_dclk;
+ }
+
+ ret = clk_enable(ctx->aclk);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable aclk\n");
+ goto err_aclk;
+ }
+ ctx->clk_on = true;
+ }
+
+ return ret;
+err_aclk:
+ clk_disable(ctx->aclk);
+err_dclk:
+ clk_disable(ctx->dclk);
+ return ret;
+}
+
+static void vop_clk_disable(struct vop_context *ctx)
+{
+ if (ctx->clk_on) {
+ clk_disable(ctx->aclk);
+ clk_disable(ctx->dclk);
+ clk_disable(ctx->hclk);
+ ctx->clk_on = false;
+ }
+}
+
+static void vop_power_on(struct vop_context *ctx)
+{
+ if (vop_clk_enable(ctx) < 0) {
+ dev_err(ctx->dev, "failed to enable clks\n");
+ return;
+ }
+
+ spin_lock(&ctx->reg_lock);
+
+ VOP_CTRL_SET(ctx, standby, 0);
+
+ spin_unlock(&ctx->reg_lock);
+}
+
+static void vop_power_off(struct vop_context *ctx)
+{
+ spin_lock(&ctx->reg_lock);
+
+ VOP_CTRL_SET(ctx, standby, 1);
+
+ spin_unlock(&ctx->reg_lock);
+
+ vop_clk_disable(ctx);
+}
+
+/*
+ * @start: starting pixel in screen coordinates
+ * @length: length of buffer "row" in pixels
+ * @end: one pixel past end of buffer in screen coordinates
+ * @last: width of screen, or one pixel past end of screen
+ * @size: number of pixels of buffer to display on scree
+ */
+static int rockchip_plane_get_size(int start, unsigned length, unsigned last)
+{
+ int end = start + length;
+ int size = 0;
+
+ if (start <= 0) {
+ if (end > 0)
+ size = min_t(unsigned, end, last);
+ } else if (start < last) {
+ size = min_t(unsigned, last - start, length);
+ }
+
+ return size;
+}
+
+static int rockchip_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, int crtc_x,
+ int crtc_y, unsigned int crtc_w,
+ unsigned int crtc_h, uint32_t src_x,
+ uint32_t src_y, uint32_t src_w, uint32_t src_h)
+{
+ struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
+ const struct vop_win *win = rockchip_plane->win;
+ struct vop_context *ctx = to_vop_ctx(crtc);
+ struct drm_gem_object *obj;
+ struct rockchip_gem_object *rk_obj;
+ unsigned long offset;
+ unsigned int actual_w;
+ unsigned int actual_h;
+ unsigned int dsp_stx;
+ unsigned int dsp_sty;
+ unsigned int y_vir_stride;
+ dma_addr_t yrgb_mst;
+ enum vop_data_format format;
+ uint32_t val;
+ bool is_alpha;
+
+ src_w >>= 16;
+ src_h >>= 16;
+ src_x >>= 16;
+ src_y >>= 16;
+
+ if (crtc_w != src_w || crtc_h != src_h)
+ return -EINVAL;
+
+ actual_w = rockchip_plane_get_size(crtc_x,
+ crtc_w, crtc->mode.hdisplay);
+ actual_h = rockchip_plane_get_size(crtc_y,
+ crtc_h, crtc->mode.vdisplay);
+ if (!actual_w || !actual_h)
+ return -EINVAL;
+
+ if (crtc_x < 0) {
+ if (actual_w)
+ src_x -= crtc_x;
+ crtc_x = 0;
+ }
+
+ if (crtc_y < 0) {
+ if (actual_h)
+ src_y -= crtc_y;
+ crtc_y = 0;
+ }
+
+ is_alpha = is_alpha_support(fb->pixel_format);
+ format = vop_convert_format(fb->pixel_format);
+ if (format < 0)
+ return format;
+
+ obj = rockchip_fb_get_gem_obj(fb, 0);
+ if (!obj) {
+ DRM_ERROR("fail to get rockchip gem object from framebuffer\n");
+ return -EINVAL;
+ }
+
+ rk_obj = to_rockchip_obj(obj);
+
+ yrgb_mst = rk_obj->dma_addr;
+
+ dsp_stx = crtc_x + crtc->mode.htotal - crtc->mode.hsync_start;
+ dsp_sty = crtc_y + crtc->mode.vtotal - crtc->mode.vsync_start;
+
+ offset = src_x * (fb->bits_per_pixel >> 3);
+ offset += src_y * fb->pitches[0];
+
+ y_vir_stride = fb->pitches[0] / (fb->bits_per_pixel >> 3);
+
+ spin_lock(&ctx->reg_lock);
+
+ VOP_WIN_SET(ctx, win, format, format);
+ VOP_WIN_SET(ctx, win, yrgb_vir, y_vir_stride);
+ yrgb_mst += offset;
+ VOP_WIN_SET(ctx, win, yrgb_mst, yrgb_mst);
+ val = ((actual_h - 1) & 0xffff) << 16;
+ val |= (actual_w - 1) & 0xffff;
+ VOP_WIN_SET(ctx, win, act_info, val);
+ VOP_WIN_SET(ctx, win, dsp_info, val);
+ val = ((dsp_sty - 1) & 0xffff) << 16;
+ val |= (dsp_stx - 1) & 0xffff;
+ VOP_WIN_SET(ctx, win, dsp_st, val);
+
+ if (is_alpha) {
+ VOP_WIN_SET(ctx, win, dst_alpha_ctl,
+ DST_FACTOR_M0(ALPHA_SRC_INVERSE));
+ val = SRC_ALPHA_EN(1) | SRC_COLOR_M0(ALPHA_SRC_PRE_MUL) |
+ SRC_ALPHA_M0(ALPHA_STRAIGHT) |
+ SRC_BLEND_M0(ALPHA_PER_PIX) |
+ SRC_ALPHA_CAL_M0(ALPHA_NO_SATURATION) |
+ SRC_FACTOR_M0(ALPHA_ONE);
+ VOP_WIN_SET(ctx, win, src_alpha_ctl, val);
+ } else {
+ VOP_WIN_SET(ctx, win, src_alpha_ctl, SRC_ALPHA_EN(0));
+ }
+
+ VOP_WIN_SET(ctx, win, enable, 1);
+
+ spin_unlock(&ctx->reg_lock);
+
+ mutex_lock(&ctx->vsync_mutex);
+
+ /*
+ * Because the buffer set to vop take effect at frame start time,
+ * we need make sure old buffer is not in use before we release
+ * it.
+ * reference the framebuffer, and unference it when it swap out of vop.
+ */
+ if (fb != rockchip_plane->front_fb) {
+ drm_framebuffer_reference(fb);
+ if (rockchip_plane->pending_fb)
+ drm_framebuffer_unreference(rockchip_plane->pending_fb);
+ rockchip_plane->pending_fb = fb;
+ rockchip_plane->pending_yrgb_mst = yrgb_mst;
+ ctx->vsync_work_pending = true;
+ }
+ rockchip_plane->enabled = true;
+
+ mutex_unlock(&ctx->vsync_mutex);
+
+ spin_lock(&ctx->reg_lock);
+ vop_cfg_done(ctx);
+ spin_unlock(&ctx->reg_lock);
+
+ return 0;
+}
+
+#define rockchip_update_plane_helper(plane, crtc, fb, x, y) \
+ rockchip_update_plane(plane, crtc, fb, 0, 0, \
+ (fb)->width, (fb)->height, \
+ (x) << 16, (y) << 16, \
+ (fb)->width << 16, (fb)->height << 16)
+
+static int rockchip_disable_plane(struct drm_plane *plane)
+{
+ struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
+ const struct vop_win *win = rockchip_plane->win;
+ struct vop_context *ctx;
+
+ if (!plane->crtc)
+ return 0;
+
+ ctx = to_vop_ctx(plane->crtc);
+ spin_lock(&ctx->reg_lock);
+
+ VOP_WIN_SET(ctx, win, enable, 0);
+ vop_cfg_done(ctx);
+
+ spin_unlock(&ctx->reg_lock);
+
+ mutex_lock(&ctx->vsync_mutex);
+
+ /*
+ * clear the pending framebuffer and set vsync_work_pending true,
+ * so that the framebuffer will unref at the next vblank.
+ */
+ if (rockchip_plane->pending_fb) {
+ drm_framebuffer_unreference(rockchip_plane->pending_fb);
+ rockchip_plane->pending_fb = NULL;
+ }
+
+ rockchip_plane->enabled = false;
+ ctx->vsync_work_pending = true;
+
+ mutex_unlock(&ctx->vsync_mutex);
+
+ return 0;
+}
+
+static void rockchip_plane_destroy(struct drm_plane *plane)
+{
+ struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
+ struct vop_context *ctx = to_vop_ctx(plane->crtc);
+
+ rockchip_disable_plane(plane);
+ drm_plane_cleanup(plane);
+ ctx->win_mask &= ~(1 << rockchip_plane->id);
+ kfree(rockchip_plane);
+}
+
+static const struct drm_plane_funcs rockchip_plane_funcs = {
+ .update_plane = rockchip_update_plane,
+ .disable_plane = rockchip_disable_plane,
+ .destroy = rockchip_plane_destroy,
+};
+
+static struct drm_plane *rockchip_plane_init(struct vop_context *ctx,
+ unsigned long possible_crtcs,
+ enum drm_plane_type type,
+ int index)
+{
+ struct rockchip_plane *rockchip_plane;
+ struct vop_driver_data *vop_data = ctx->data;
+ const struct vop_win *win;
+ int err;
+
+ if (index >= VOP_MAX_WIN_SUPPORT)
+ return ERR_PTR(-EINVAL);
+
+ rockchip_plane = kzalloc(sizeof(*rockchip_plane), GFP_KERNEL);
+ if (!rockchip_plane)
+ return ERR_PTR(-ENOMEM);
+
+ win = vop_data->win[index];
+ ctx->win_mask |= (1 << index);
+ rockchip_plane->id = index;
+ rockchip_plane->win = win;
+
+ err = drm_universal_plane_init(ctx->drm_dev, &rockchip_plane->base,
+ possible_crtcs, &rockchip_plane_funcs,
+ win->phy->data_formats,
+ win->phy->nformats, type);
+ if (err) {
+ DRM_ERROR("failed to initialize plane\n");
+ kfree(rockchip_plane);
+ return ERR_PTR(err);
+ }
+
+ return &rockchip_plane->base;
+}
+
+int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type)
+{
+ struct vop_context *ctx = to_vop_ctx(crtc);
+
+ if (ctx->dpms != DRM_MODE_DPMS_ON)
+ return -EPERM;
+
+ switch (connector_type) {
+ case DRM_MODE_CONNECTOR_LVDS:
+ VOP_CTRL_SET(ctx, rgb_en, 1);
+ VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_P888);
+ break;
+ case DRM_MODE_CONNECTOR_eDP:
+ VOP_CTRL_SET(ctx, edp_en, 1);
+ VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_AAAA);
+ break;
+ case DRM_MODE_CONNECTOR_HDMIA:
+ VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_AAAA);
+ VOP_CTRL_SET(ctx, hdmi_en, 1);
+ break;
+ default:
+ DRM_ERROR("unsupport connector_type[%d]\n", connector_type);
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe)
+{
+ struct vop_context *ctx = to_vop_ctx(rockchip_drm_find_crtc(dev, pipe));
+ unsigned long flags;
+
+ if (ctx->dpms != DRM_MODE_DPMS_ON)
+ return -EPERM;
+
+ spin_lock_irqsave(&ctx->irq_lock, flags);
+
+ vop_mask_write(ctx, INTR_CTRL0, LINE_FLAG_INTR_MASK,
+ LINE_FLAG_INTR_EN(1));
+
+ spin_unlock_irqrestore(&ctx->irq_lock, flags);
+
+ return 0;
+}
+
+void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe)
+{
+ struct vop_context *ctx = to_vop_ctx(rockchip_drm_find_crtc(dev, pipe));
+ unsigned long flags;
+
+ if (ctx->dpms != DRM_MODE_DPMS_ON)
+ return;
+ spin_lock_irqsave(&ctx->irq_lock, flags);
+ vop_mask_write(ctx, INTR_CTRL0, LINE_FLAG_INTR_MASK,
+ LINE_FLAG_INTR_EN(0));
+ spin_unlock_irqrestore(&ctx->irq_lock, flags);
+}
+
+static void rockchip_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct vop_context *ctx = to_vop_ctx(crtc);
+
+ DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
+
+ if (ctx->dpms == mode) {
+ DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
+ return;
+ }
+ if (mode > DRM_MODE_DPMS_ON) {
+ /* wait for the completion of page flip. */
+ if (!wait_event_timeout(ctx->wait_vsync_queue,
+ !atomic_read(&ctx->wait_vsync_event),
+ HZ/20))
+ DRM_DEBUG_KMS("vblank wait timed out.\n");
+ drm_vblank_off(crtc->dev, ctx->pipe);
+ }
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ vop_power_on(ctx);
+ break;
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
+ case DRM_MODE_DPMS_OFF:
+ vop_power_off(ctx);
+ break;
+ default:
+ DRM_DEBUG_KMS("unspecified mode %d\n", mode);
+ break;
+ }
+
+ ctx->dpms = mode;
+}
+
+static void rockchip_drm_crtc_prepare(struct drm_crtc *crtc)
+{
+ rockchip_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static bool rockchip_drm_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ if (adjusted_mode->htotal == 0 || adjusted_mode->vtotal == 0)
+ return false;
+
+ return true;
+}
+
+static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb);
+
+static int rockchip_drm_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y,
+ struct drm_framebuffer *fb)
+{
+ struct vop_context *ctx = to_vop_ctx(crtc);
+ u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
+ u16 hdisplay = adjusted_mode->hdisplay;
+ u16 htotal = adjusted_mode->htotal;
+ u16 hact_st = adjusted_mode->htotal - adjusted_mode->hsync_start;
+ u16 hact_end = hact_st + hdisplay;
+ u16 vdisplay = adjusted_mode->vdisplay;
+ u16 vtotal = adjusted_mode->vtotal;
+ u16 vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
+ u16 vact_st = adjusted_mode->vtotal - adjusted_mode->vsync_start;
+ u16 vact_end = vact_st + vdisplay;
+ unsigned long flags;
+ int ret;
+ uint32_t val;
+
+ clk_disable(ctx->dclk);
+
+ ret = rockchip_drm_crtc_mode_set_base(crtc, x, y, fb);
+ if (ret)
+ return ret;
+
+ val = 0x8;
+ val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0;
+ val |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? (1 << 1) : 0;
+ VOP_CTRL_SET(ctx, pin_pol, val);
+
+ VOP_CTRL_SET(ctx, htotal_pw, (htotal << 16) | hsync_len);
+ val = hact_st << 16;
+ val |= hact_end;
+ VOP_CTRL_SET(ctx, hact_st_end, val);
+ VOP_CTRL_SET(ctx, hpost_st_end, val);
+
+ VOP_CTRL_SET(ctx, vtotal_pw, (vtotal << 16) | vsync_len);
+ val = vact_st << 16;
+ val |= vact_end;
+ VOP_CTRL_SET(ctx, vact_st_end, val);
+ VOP_CTRL_SET(ctx, vpost_st_end, val);
+
+ spin_lock_irqsave(&ctx->irq_lock, flags);
+
+ vop_mask_write(ctx, INTR_CTRL0, DSP_LINE_NUM_MASK,
+ DSP_LINE_NUM(vact_end));
+
+ spin_unlock_irqrestore(&ctx->irq_lock, flags);
+
+ /*
+ * do dclk_reset, let all config take affect, so new clk will safely
+ * run in correct frame.
+ */
+ reset_control_assert(ctx->dclk_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(ctx->dclk_rst);
+
+ clk_set_rate(ctx->dclk, adjusted_mode->clock * 1000);
+ clk_enable(ctx->dclk);
+
+ return 0;
+}
+
+static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ int ret;
+
+ ret = rockchip_update_plane_helper(crtc->primary, crtc,
+ crtc->primary->fb, x, y);
+ if (ret < 0) {
+ DRM_ERROR("fail to update plane\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rockchip_drm_crtc_commit(struct drm_crtc *crtc)
+{
+ /* just do dummy now */
+}
+
+static const struct drm_crtc_helper_funcs rockchip_crtc_helper_funcs = {
+ .dpms = rockchip_drm_crtc_dpms,
+ .prepare = rockchip_drm_crtc_prepare,
+ .mode_fixup = rockchip_drm_crtc_mode_fixup,
+ .mode_set = rockchip_drm_crtc_mode_set,
+ .mode_set_base = rockchip_drm_crtc_mode_set_base,
+ .commit = rockchip_drm_crtc_commit,
+};
+
+static int rockchip_drm_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags)
+{
+ struct drm_device *dev = crtc->dev;
+ struct vop_context *ctx = to_vop_ctx(crtc);
+ struct drm_framebuffer *old_fb = crtc->primary->fb;
+ int ret;
+
+ /* when the page flip is requested, crtc's dpms should be on */
+ if (ctx->dpms > DRM_MODE_DPMS_ON) {
+ DRM_DEBUG("failed page flip request at dpms[%d].\n", ctx->dpms);
+ return 0;
+ }
+
+ ret = drm_vblank_get(dev, ctx->pipe);
+ if (ret) {
+ DRM_DEBUG("failed to acquire vblank counter\n");
+ return ret;
+ }
+
+ spin_lock_irq(&dev->event_lock);
+ if (ctx->event) {
+ spin_unlock_irq(&dev->event_lock);
+ DRM_ERROR("already pending flip!\n");
+ return -EBUSY;
+ }
+ ctx->event = event;
+ atomic_set(&ctx->wait_vsync_event, 1);
+ spin_unlock_irq(&dev->event_lock);
+
+ crtc->primary->fb = fb;
+
+ ret = rockchip_update_plane_helper(crtc->primary, crtc, fb,
+ crtc->x, crtc->y);
+ if (ret) {
+ crtc->primary->fb = old_fb;
+
+ spin_lock_irq(&dev->event_lock);
+ drm_vblank_put(dev, ctx->pipe);
+ atomic_set(&ctx->wait_vsync_event, 0);
+ ctx->event = NULL;
+ spin_unlock_irq(&dev->event_lock);
+ }
+
+ return ret;
+}
+
+static void rockchip_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe)
+{
+ struct rockchip_drm_private *dev_priv = dev->dev_private;
+ struct drm_crtc *drm_crtc = dev_priv->crtc[pipe];
+ struct vop_context *ctx;
+ unsigned long flags;
+
+ ctx = to_vop_ctx(drm_crtc);
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+
+ if (ctx->event) {
+ drm_send_vblank_event(dev, -1, ctx->event);
+ drm_vblank_put(dev, pipe);
+ atomic_set(&ctx->wait_vsync_event, 0);
+ wake_up(&ctx->wait_vsync_queue);
+ ctx->event = NULL;
+ }
+
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static void rockchip_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct vop_context *ctx = to_vop_ctx(crtc);
+ struct rockchip_drm_private *private = crtc->dev->dev_private;
+
+ private->crtc[ctx->pipe] = NULL;
+ drm_crtc_cleanup(crtc);
+}
+
+static const struct drm_crtc_funcs rockchip_crtc_funcs = {
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = rockchip_drm_crtc_page_flip,
+ .destroy = rockchip_drm_crtc_destroy,
+};
+
+static void rockchip_vsync_worker(struct work_struct *work)
+{
+ struct vop_context *ctx = container_of(work, struct vop_context,
+ vsync_work);
+ struct drm_device *drm = ctx->drm_dev;
+ struct rockchip_drm_private *dev_priv = drm->dev_private;
+ struct drm_crtc *crtc = dev_priv->crtc[ctx->pipe];
+ struct rockchip_plane *rockchip_plane;
+ struct drm_plane *plane;
+ uint32_t yrgb_mst;
+
+ mutex_lock(&ctx->vsync_mutex);
+
+ ctx->vsync_work_pending = false;
+
+ list_for_each_entry(plane, &drm->mode_config.plane_list, head) {
+ rockchip_plane = to_rockchip_plane(plane);
+
+ if (to_vop_ctx(plane->crtc) != ctx)
+ continue;
+ if (rockchip_plane->enabled && !rockchip_plane->pending_fb)
+ continue;
+ if (!rockchip_plane->enabled && !rockchip_plane->front_fb)
+ continue;
+ /*
+ * make sure the yrgb_mst take effect, so that
+ * we can unreference the old framebuffer.
+ */
+ yrgb_mst = VOP_WIN_GET_YRGBADDR(ctx, rockchip_plane->win);
+ if (rockchip_plane->pending_yrgb_mst != yrgb_mst) {
+ /*
+ * some plane no complete, unref at next vblank
+ */
+ ctx->vsync_work_pending = true;
+ continue;
+ }
+
+ /*
+ * drm_framebuffer_unreference maybe call iommu unmap,
+ * and iommu not allow unmap buffer at irq context,
+ * so we do drm_framebuffer_unreference at queue_work.
+ */
+ if (rockchip_plane->front_fb)
+ drm_framebuffer_unreference(rockchip_plane->front_fb);
+
+ rockchip_plane->front_fb = rockchip_plane->pending_fb;
+ rockchip_plane->pending_fb = NULL;
+
+ /*
+ * if primary plane flip complete, sending the event to
+ * userspace
+ */
+ if (&rockchip_plane->base == crtc->primary)
+ rockchip_drm_crtc_finish_pageflip(ctx->drm_dev,
+ ctx->pipe);
+ }
+
+ mutex_unlock(&ctx->vsync_mutex);
+}
+
+static irqreturn_t rockchip_vop_isr(int irq, void *data)
+{
+ struct vop_context *ctx = data;
+ uint32_t intr0_reg;
+ unsigned long flags;
+
+ intr0_reg = vop_readl(ctx, INTR_CTRL0);
+ if (intr0_reg & LINE_FLAG_INTR) {
+ spin_lock_irqsave(&ctx->irq_lock, flags);
+ vop_writel(ctx, INTR_CTRL0, intr0_reg | LINE_FLAG_INTR_CLR);
+ spin_unlock_irqrestore(&ctx->irq_lock, flags);
+ } else {
+ return IRQ_NONE;
+ }
+
+ drm_handle_vblank(ctx->drm_dev, ctx->pipe);
+ if (ctx->vsync_work_pending)
+ queue_work(ctx->vsync_wq, &ctx->vsync_work);
+
+ return IRQ_HANDLED;
+}
+
+static int vop_create_crtc(struct vop_context *ctx)
+{
+ struct device *dev = ctx->dev;
+ struct drm_device *drm_dev = ctx->drm_dev;
+ struct drm_plane *primary, *cursor;
+ unsigned long possible_crtcs;
+ struct drm_crtc *crtc;
+ int ret;
+ int nr;
+
+ ctx->win_mask = 0;
+ crtc = &ctx->crtc;
+
+ ret = rockchip_drm_add_crtc(drm_dev, crtc, dev->of_node);
+ if (ret < 0)
+ return ret;
+ ctx->pipe = ret;
+
+ possible_crtcs = (1 << ctx->pipe);
+
+ for (nr = 0; nr < VOP_MAX_WIN_SUPPORT; nr++) {
+ if (nr == VOP_DEFAULT_PRIMARY) {
+ primary = rockchip_plane_init(ctx, possible_crtcs,
+ DRM_PLANE_TYPE_PRIMARY,
+ VOP_DEFAULT_PRIMARY);
+ if (IS_ERR(primary)) {
+ DRM_ERROR("fail to init primary plane\n");
+ return PTR_ERR(primary);
+ }
+ } else if (nr == VOP_DEFAULT_CURSOR) {
+ cursor = rockchip_plane_init(ctx, possible_crtcs,
+ DRM_PLANE_TYPE_CURSOR,
+ VOP_DEFAULT_CURSOR);
+ if (IS_ERR(cursor)) {
+ DRM_ERROR("fail to init cursor plane\n");
+ return PTR_ERR(cursor);
+ }
+ } else {
+ struct drm_plane *plane;
+
+ plane = rockchip_plane_init(ctx, possible_crtcs,
+ DRM_PLANE_TYPE_OVERLAY,
+ nr);
+ if (IS_ERR(plane)) {
+ DRM_ERROR("fail to init overlay plane\n");
+ return PTR_ERR(plane);
+ }
+ }
+ }
+
+ drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
+ &rockchip_crtc_funcs);
+ drm_crtc_helper_add(crtc, &rockchip_crtc_helper_funcs);
+
+ return 0;
+}
+
+static int rockchip_vop_initial(struct vop_context *ctx)
+{
+ struct vop_driver_data *vop_data = ctx->data;
+ const struct vop_reg_data *init_table = vop_data->init_table;
+ struct reset_control *ahb_rst;
+ int i, ret;
+
+ ctx->hclk = devm_clk_get(ctx->dev, "hclk_vop");
+ if (IS_ERR(ctx->hclk)) {
+ dev_err(ctx->dev, "failed to get hclk source\n");
+ return PTR_ERR(ctx->hclk);
+ }
+ ctx->aclk = devm_clk_get(ctx->dev, "aclk_vop");
+ if (IS_ERR(ctx->aclk)) {
+ dev_err(ctx->dev, "failed to get aclk source\n");
+ return PTR_ERR(ctx->aclk);
+ }
+ ctx->dclk = devm_clk_get(ctx->dev, "dclk_vop");
+ if (IS_ERR(ctx->dclk)) {
+ dev_err(ctx->dev, "failed to get dclk source\n");
+ return PTR_ERR(ctx->dclk);
+ }
+
+ ret = clk_prepare(ctx->hclk);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to prepare hclk\n");
+ return ret;
+ }
+
+ ret = clk_prepare(ctx->dclk);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to prepare dclk\n");
+ goto err_prepare_hclk;
+ }
+
+ ret = clk_prepare(ctx->aclk);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to prepare aclk\n");
+ goto err_prepare_dclk;
+ }
+
+ ret = vop_clk_enable(ctx);
+ if (ret < 0)
+ goto err_prepare_aclk;
+
+ /*
+ * do hclk_reset, reset all vop registers.
+ */
+ ahb_rst = devm_reset_control_get(ctx->dev, "ahb");
+ if (IS_ERR(ahb_rst)) {
+ dev_err(ctx->dev, "failed to get ahb reset\n");
+ ret = PTR_ERR(ahb_rst);
+ goto err_diable_clk;
+ }
+ reset_control_assert(ahb_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(ahb_rst);
+
+ memcpy(ctx->regsbak, ctx->regs, ctx->len);
+
+ for (i = 0; i < vop_data->table_size; i++)
+ vop_writel(ctx, init_table[i].offset, init_table[i].value);
+
+ for (i = 0; i < VOP_MAX_WIN_SUPPORT; i++)
+ VOP_WIN_SET(ctx, vop_data->win[i], enable, 0);
+
+ vop_cfg_done(ctx);
+
+ /*
+ * do dclk_reset, let all win config take affect, and then we can enable
+ * iommu safe.
+ */
+ ctx->dclk_rst = devm_reset_control_get(ctx->dev, "dclk");
+ if (IS_ERR(ctx->dclk_rst)) {
+ dev_err(ctx->dev, "failed to get dclk reset\n");
+ ret = PTR_ERR(ctx->dclk_rst);
+ goto err_diable_clk;
+ }
+ reset_control_assert(ctx->dclk_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(ctx->dclk_rst);
+
+ ctx->dpms = DRM_MODE_DPMS_ON;
+
+ return 0;
+
+err_prepare_aclk:
+ clk_unprepare(ctx->aclk);
+err_prepare_dclk:
+ clk_unprepare(ctx->dclk);
+err_prepare_hclk:
+ clk_unprepare(ctx->hclk);
+err_diable_clk:
+ vop_clk_disable(ctx);
+ return ret;
+}
+
+static int vop_bind(struct device *dev, struct device *master, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct vop_driver_data *vop_data = vop_get_driver_data(dev);
+ struct drm_device *drm_dev = data;
+ struct vop_context *ctx;
+ struct resource *res;
+ int ret;
+
+ if (!vop_data)
+ return -ENODEV;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ ctx->data = vop_data;
+ ctx->drm_dev = drm_dev;
+ dev_set_drvdata(dev, ctx);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ctx->len = resource_size(res);
+ ctx->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(ctx->regs))
+ return PTR_ERR(ctx->regs);
+
+ ctx->regsbak = devm_kzalloc(dev, ctx->len, GFP_KERNEL);
+ if (!ctx->regsbak)
+ return -ENOMEM;
+
+ ret = rockchip_vop_initial(ctx);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot initial vop dev - err %d\n", ret);
+ return ret;
+ }
+
+ ctx->irq = platform_get_irq(pdev, 0);
+ if (ctx->irq < 0) {
+ dev_err(dev, "cannot find irq for vop\n");
+ return ctx->irq;
+ }
+
+ spin_lock_init(&ctx->reg_lock);
+ spin_lock_init(&ctx->irq_lock);
+
+ init_waitqueue_head(&ctx->wait_vsync_queue);
+ atomic_set(&ctx->wait_vsync_event, 0);
+
+ ret = vop_iommu_init(ctx);
+ if (ret) {
+ DRM_ERROR("Failed to setup iommu, %d\n", ret);
+ return ret;
+ }
+
+ ctx->vsync_wq = create_singlethread_workqueue("vsync");
+ if (!ctx->vsync_wq) {
+ dev_err(dev, "failed to create workqueue\n");
+ return -EINVAL;
+ }
+ INIT_WORK(&ctx->vsync_work, rockchip_vsync_worker);
+
+ mutex_init(&ctx->vsync_mutex);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_request_irq(dev, ctx->irq, rockchip_vop_isr,
+ IRQF_SHARED, dev_name(dev), ctx);
+ if (ret) {
+ dev_err(dev, "cannot requeset irq%d - err %d\n", ctx->irq, ret);
+ return ret;
+ }
+
+ return vop_create_crtc(ctx);
+}
+
+static void vop_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct drm_device *drm_dev = data;
+ struct vop_context *ctx = dev_get_drvdata(dev);
+ struct drm_crtc *crtc = &ctx->crtc;
+
+ drm_crtc_cleanup(crtc);
+ pm_runtime_disable(dev);
+ rockchip_drm_remove_crtc(drm_dev, ctx->pipe);
+
+ vop_iommu_fini(ctx);
+}
+
+static const struct component_ops vop_component_ops = {
+ .bind = vop_bind,
+ .unbind = vop_unbind,
+};
+
+static int vop_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct vop_context *ctx;
+
+ if (!dev->of_node) {
+ dev_err(dev, "can't find vop devices\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, ctx);
+
+ return component_add(dev, &vop_component_ops);
+}
+
+static int vop_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &vop_component_ops);
+
+ return 0;
+}
+
+struct platform_driver rockchip_vop_platform_driver = {
+ .probe = vop_probe,
+ .remove = vop_remove,
+ .driver = {
+ .name = "rockchip-vop",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(vop_driver_dt_match),
+ },
+};
+
+module_platform_driver(rockchip_vop_platform_driver);
+
+MODULE_AUTHOR("Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>");
+MODULE_DESCRIPTION("ROCKCHIP VOP Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
new file mode 100644
index 0000000..d2a04f0
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
+ *
+ * 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.
+ */
+
+#ifndef _ROCKCHIP_DRM_VOP_H
+#define _ROCKCHIP_DRM_VOP_H
+
+/* register definition */
+#define REG_CFG_DONE 0x0000
+#define VERSION_INFO 0x0004
+#define SYS_CTRL 0x0008
+#define SYS_CTRL1 0x000c
+#define DSP_CTRL0 0x0010
+#define DSP_CTRL1 0x0014
+#define DSP_BG 0x0018
+#define MCU_CTRL 0x001c
+#define INTR_CTRL0 0x0020
+#define INTR_CTRL1 0x0024
+#define WIN0_CTRL0 0x0030
+#define WIN0_CTRL1 0x0034
+#define WIN0_COLOR_KEY 0x0038
+#define WIN0_VIR 0x003c
+#define WIN0_YRGB_MST 0x0040
+#define WIN0_CBR_MST 0x0044
+#define WIN0_ACT_INFO 0x0048
+#define WIN0_DSP_INFO 0x004c
+#define WIN0_DSP_ST 0x0050
+#define WIN0_SCL_FACTOR_YRGB 0x0054
+#define WIN0_SCL_FACTOR_CBR 0x0058
+#define WIN0_SCL_OFFSET 0x005c
+#define WIN0_SRC_ALPHA_CTRL 0x0060
+#define WIN0_DST_ALPHA_CTRL 0x0064
+#define WIN0_FADING_CTRL 0x0068
+/* win1 register */
+#define WIN1_CTRL0 0x0070
+#define WIN1_CTRL1 0x0074
+#define WIN1_COLOR_KEY 0x0078
+#define WIN1_VIR 0x007c
+#define WIN1_YRGB_MST 0x0080
+#define WIN1_CBR_MST 0x0084
+#define WIN1_ACT_INFO 0x0088
+#define WIN1_DSP_INFO 0x008c
+#define WIN1_DSP_ST 0x0090
+#define WIN1_SCL_FACTOR_YRGB 0x0094
+#define WIN1_SCL_FACTOR_CBR 0x0098
+#define WIN1_SCL_OFFSET 0x009c
+#define WIN1_SRC_ALPHA_CTRL 0x00a0
+#define WIN1_DST_ALPHA_CTRL 0x00a4
+#define WIN1_FADING_CTRL 0x00a8
+/* win2 register */
+#define WIN2_CTRL0 0x00b0
+#define WIN2_CTRL1 0x00b4
+#define WIN2_VIR0_1 0x00b8
+#define WIN2_VIR2_3 0x00bc
+#define WIN2_MST0 0x00c0
+#define WIN2_DSP_INFO0 0x00c4
+#define WIN2_DSP_ST0 0x00c8
+#define WIN2_COLOR_KEY 0x00cc
+#define WIN2_MST1 0x00d0
+#define WIN2_DSP_INFO1 0x00d4
+#define WIN2_DSP_ST1 0x00d8
+#define WIN2_SRC_ALPHA_CTRL 0x00dc
+#define WIN2_MST2 0x00e0
+#define WIN2_DSP_INFO2 0x00e4
+#define WIN2_DSP_ST2 0x00e8
+#define WIN2_DST_ALPHA_CTRL 0x00ec
+#define WIN2_MST3 0x00f0
+#define WIN2_DSP_INFO3 0x00f4
+#define WIN2_DSP_ST3 0x00f8
+#define WIN2_FADING_CTRL 0x00fc
+/* win3 register */
+#define WIN3_CTRL0 0x0100
+#define WIN3_CTRL1 0x0104
+#define WIN3_VIR0_1 0x0108
+#define WIN3_VIR2_3 0x010c
+#define WIN3_MST0 0x0110
+#define WIN3_DSP_INFO0 0x0114
+#define WIN3_DSP_ST0 0x0118
+#define WIN3_COLOR_KEY 0x011c
+#define WIN3_MST1 0x0120
+#define WIN3_DSP_INFO1 0x0124
+#define WIN3_DSP_ST1 0x0128
+#define WIN3_SRC_ALPHA_CTRL 0x012c
+#define WIN3_MST2 0x0130
+#define WIN3_DSP_INFO2 0x0134
+#define WIN3_DSP_ST2 0x0138
+#define WIN3_DST_ALPHA_CTRL 0x013c
+#define WIN3_MST3 0x0140
+#define WIN3_DSP_INFO3 0x0144
+#define WIN3_DSP_ST3 0x0148
+#define WIN3_FADING_CTRL 0x014c
+/* hwc register */
+#define HWC_CTRL0 0x0150
+#define HWC_CTRL1 0x0154
+#define HWC_MST 0x0158
+#define HWC_DSP_ST 0x015c
+#define HWC_SRC_ALPHA_CTRL 0x0160
+#define HWC_DST_ALPHA_CTRL 0x0164
+#define HWC_FADING_CTRL 0x0168
+/* post process register */
+#define POST_DSP_HACT_INFO 0x0170
+#define POST_DSP_VACT_INFO 0x0174
+#define POST_SCL_FACTOR_YRGB 0x0178
+#define POST_SCL_CTRL 0x0180
+#define POST_DSP_VACT_INFO_F1 0x0184
+#define DSP_HTOTAL_HS_END 0x0188
+#define DSP_HACT_ST_END 0x018c
+#define DSP_VTOTAL_VS_END 0x0190
+#define DSP_VACT_ST_END 0x0194
+#define DSP_VS_ST_END_F1 0x0198
+#define DSP_VACT_ST_END_F1 0x019c
+/* register definition end */
+
+/* interrupt define */
+#define DSP_HOLD_VALID_INTR (1 << 0)
+#define FS_INTR (1 << 1)
+#define LINE_FLAG_INTR (1 << 2)
+#define BUS_ERROR_INTR (1 << 3)
+
+#define DSP_HOLD_VALID_INTR_EN(x) ((x) << 4)
+#define FS_INTR_EN(x) ((x) << 5)
+#define LINE_FLAG_INTR_EN(x) ((x) << 6)
+#define BUS_ERROR_INTR_EN(x) ((x) << 7)
+#define DSP_HOLD_VALID_INTR_MASK (1 << 4)
+#define FS_INTR_EN_MASK (1 << 5)
+#define LINE_FLAG_INTR_MASK (1 << 6)
+#define BUS_ERROR_INTR_MASK (1 << 7)
+
+#define DSP_HOLD_VALID_INTR_CLR (1 << 8)
+#define FS_INTR_EN_CLR (1 << 9)
+#define LINE_FLAG_INTR_CLR (1 << 10)
+#define BUS_ERROR_INTR_CLR (1 << 11)
+#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
+#define DSP_LINE_NUM_MASK (0x1fff << 12)
+
+/* src alpha ctrl define */
+#define SRC_FADING_VALUE(x) (((x) & 0xff) << 24)
+#define SRC_GLOBAL_ALPHA(x) (((x) & 0xff) << 16)
+#define SRC_FACTOR_M0(x) (((x) & 0x7) << 6)
+#define SRC_ALPHA_CAL_M0(x) (((x) & 0x1) << 5)
+#define SRC_BLEND_M0(x) (((x) & 0x3) << 3)
+#define SRC_ALPHA_M0(x) (((x) & 0x1) << 2)
+#define SRC_COLOR_M0(x) (((x) & 0x1) << 1)
+#define SRC_ALPHA_EN(x) (((x) & 0x1) << 0)
+/* dst alpha ctrl define */
+#define DST_FACTOR_M0(x) (((x) & 0x7) << 6)
+
+/*
+ * display output interface supported by rockchip lcdc
+ */
+#define ROCKCHIP_OUT_MODE_P888 0
+#define ROCKCHIP_OUT_MODE_P666 1
+#define ROCKCHIP_OUT_MODE_P565 2
+/* for use special outface */
+#define ROCKCHIP_OUT_MODE_AAAA 15
+
+enum alpha_mode {
+ ALPHA_STRAIGHT,
+ ALPHA_INVERSE,
+};
+
+enum global_blend_mode {
+ ALPHA_GLOBAL,
+ ALPHA_PER_PIX,
+ ALPHA_PER_PIX_GLOBAL,
+};
+
+enum alpha_cal_mode {
+ ALPHA_SATURATION,
+ ALPHA_NO_SATURATION,
+};
+
+enum color_mode {
+ ALPHA_SRC_PRE_MUL,
+ ALPHA_SRC_NO_PRE_MUL,
+};
+
+enum factor_mode {
+ ALPHA_ZERO,
+ ALPHA_ONE,
+ ALPHA_SRC,
+ ALPHA_SRC_INVERSE,
+ ALPHA_SRC_GLOBAL,
+};
+
+#endif /* _ROCKCHIP_DRM_VOP_H */
--
1.7.9.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [RESENT PATCH v7 2/3] dt-bindings: video: Add for rockchip display subsytem
2014-09-26 10:53 [RESENT PATCH v7 0/3] Add drm driver for Rockchip Socs Mark Yao
[not found] ` <1411728800-25112-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
@ 2014-09-26 10:57 ` Mark Yao
2014-09-26 10:58 ` [RESENT PATCH v7 3/3] dt-bindings: video: Add documentation for rockchip vop Mark Yao
2 siblings, 0 replies; 6+ messages in thread
From: Mark Yao @ 2014-09-26 10:57 UTC (permalink / raw)
To: heiko, Boris BREZILLON, David Airlie, Rob Clark, Daniel Vetter,
Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Randy Dunlap, Grant Likely, Greg Kroah-Hartman, John Stultz,
Rom Lemarchand
Cc: devicetree, linux-doc, linux-kernel, dri-devel, linux-api,
linux-rockchip, dianders, marcheu, dbehr, olof, djkurtz, xjq, kfx,
cym, cf, zyw, xxm, huangtao, kever.yang, yxj, wxt, xw, Mark yao
From: Mark yao <mark.yao@rock-chips.com>
This add a display subsystem comprise the all display interface nodes.
Signed-off-by: Mark Yao <mark.yao@rock-chips.com>
Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Acked-by: Daniel Vetter <daniel@ffwll.ch>
Reviewed-by: Rob Clark <robdclark@gmail.com>
---
Changes in v2:
- add DRM master device node to list all display nodes that comprise
the graphics subsystem.
Changes in v3: None
Changes in v4: None
Changes in v5: None
Changes in v6: None
Changes in v7: None
.../devicetree/bindings/video/rockchip-drm.txt | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 Documentation/devicetree/bindings/video/rockchip-drm.txt
diff --git a/Documentation/devicetree/bindings/video/rockchip-drm.txt b/Documentation/devicetree/bindings/video/rockchip-drm.txt
new file mode 100644
index 0000000..7fff582
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/rockchip-drm.txt
@@ -0,0 +1,19 @@
+Rockchip DRM master device
+================================
+
+The Rockchip DRM master device is a virtual device needed to list all
+vop devices or other display interface nodes that comprise the
+graphics subsystem.
+
+Required properties:
+- compatible: Should be "rockchip,display-subsystem"
+- ports: Should contain a list of phandles pointing to display interface port
+ of vop devices. vop definitions as defined in
+ Documentation/devicetree/bindings/video/rockchip-vop.txt
+
+example:
+
+display-subsystem {
+ compatible = "rockchip,display-subsystem";
+ ports = <&vopl_out>, <&vopb_out>;
+};
--
1.7.9.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [RESENT PATCH v7 3/3] dt-bindings: video: Add documentation for rockchip vop
2014-09-26 10:53 [RESENT PATCH v7 0/3] Add drm driver for Rockchip Socs Mark Yao
[not found] ` <1411728800-25112-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
2014-09-26 10:57 ` [RESENT PATCH v7 2/3] dt-bindings: video: Add for rockchip display subsytem Mark Yao
@ 2014-09-26 10:58 ` Mark Yao
2 siblings, 0 replies; 6+ messages in thread
From: Mark Yao @ 2014-09-26 10:58 UTC (permalink / raw)
To: heiko, Boris BREZILLON, David Airlie, Rob Clark, Daniel Vetter,
Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Randy Dunlap, Grant Likely, Greg Kroah-Hartman, John Stultz,
Rom Lemarchand
Cc: devicetree, linux-doc, linux-kernel, dri-devel, linux-api,
linux-rockchip, dianders, marcheu, dbehr, olof, djkurtz, xjq, kfx,
cym, cf, zyw, xxm, huangtao, kever.yang, yxj, wxt, xw, Mark yao
From: Mark yao <mark.yao@rock-chips.com>
This adds binding documentation for Rockchip SoC VOP driver.
Signed-off-by: Mark Yao <mark.yao@rock-chips.com>
Acked-by: Daniel Vetter <daniel@ffwll.ch>
Reviewed-by: Rob Clark <robdclark@gmail.com>
---
Changes in v2:
- rename "lcdc" to "vop"
- add vop reset
- add iommu node
- add port for display-subsystem
Changes in v3: None
Changes in v4: None
Changes in v5: None
Changes in v6: None
Changes in v7: None
.../devicetree/bindings/video/rockchip-vop.txt | 58 ++++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 Documentation/devicetree/bindings/video/rockchip-vop.txt
diff --git a/Documentation/devicetree/bindings/video/rockchip-vop.txt b/Documentation/devicetree/bindings/video/rockchip-vop.txt
new file mode 100644
index 0000000..d15351f
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/rockchip-vop.txt
@@ -0,0 +1,58 @@
+device-tree bindings for rockchip soc display controller (vop)
+
+VOP (Visual Output Processor) is the Display Controller for the Rockchip
+series of SoCs which transfers the image data from a video memory
+buffer to an external LCD interface.
+
+Required properties:
+- compatible: value should be one of the following
+ "rockchip,rk3288-vop";
+
+- interrupts: should contain a list of all VOP IP block interrupts in the
+ order: VSYNC, LCD_SYSTEM. The interrupt specifier
+ format depends on the interrupt controller used.
+
+- clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+
+- clock-names: Must contain
+ aclk_vop: for ddr buffer transfer.
+ hclk_vop: for ahb bus to R/W the phy regs.
+ dclk_vop: pixel clock.
+
+- resets: Must contain an entry for each entry in reset-names.
+ See ../reset/reset.txt for details.
+- reset-names: Must include the following entries:
+ - axi
+ - ahb
+ - dclk
+
+- iommus: required a iommu node
+
+- port: A port node with endpoint definitions as defined in
+ Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Example:
+SoC specific DT entry:
+ vopb: vopb@ff930000 {
+ compatible = "rockchip,rk3288-vop";
+ reg = <0xff930000 0x19c>;
+ interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>;
+ clock-names = "aclk_vop", "dclk_vop", "hclk_vop";
+ resets = <&cru SRST_LCDC1_AXI>, <&cru SRST_LCDC1_AHB>, <&cru SRST_LCDC1_DCLK>;
+ reset-names = "axi", "ahb", "dclk";
+ iommus = <&vopb_mmu>;
+ vopb_out: port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ vopb_out_edp: endpoint@0 {
+ reg = <0>;
+ remote-endpoint=<&edp_in_vopb>;
+ };
+ vopb_out_hdmi: endpoint@1 {
+ reg = <1>;
+ remote-endpoint=<&hdmi_in_vopb>;
+ };
+ };
+ };
--
1.7.9.5
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [RESENT PATCH v7 1/3] drm: rockchip: Add basic drm driver
[not found] ` <1411728904-25182-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
@ 2014-09-29 4:01 ` Daniel Kurtz
2014-09-30 0:35 ` Mark yao
0 siblings, 1 reply; 6+ messages in thread
From: Daniel Kurtz @ 2014-09-29 4:01 UTC (permalink / raw)
To: Mark Yao
Cc: Heiko Stübner, Boris BREZILLON, David Airlie, Rob Clark,
Daniel Vetter, Rob Herring, Pawel Moll, Mark Rutland,
Ian Campbell, Kumar Gala, Randy Dunlap, Grant Likely,
Greg Kroah-Hartman, John Stultz, Rom Lemarchand,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, dri-devel,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Douglas Anderson
Hi Mark,
More review comments inline... (sorry for the delay)
On Fri, Sep 26, 2014 at 6:55 PM, Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org> wrote:
> From: Mark yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
>
> This patch adds the basic structure of a DRM Driver for Rockchip Socs.
>
> Signed-off-by: Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> Signed-off-by: Daniel Kurtz <djkurtz-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
> Acked-by: Daniel Vetter <daniel-/w4YWyX8dFk@public.gmane.org>
> Reviewed-by: Rob Clark <robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> Changes in v2:
> - use the component framework to defer main drm driver probe
> until all VOP devices have been probed.
> - use dma-mapping API with ARM_DMA_USE_IOMMU, create dma mapping by
> master device and each vop device can shared the drm dma mapping.
> - use drm_crtc_init_with_planes and drm_universal_plane_init.
> - remove unnecessary middle layers.
> - add cursor set, move funcs to rockchip drm crtc.
> - use vop reset at first init
> - reference framebuffer when used and unreference when swap out vop
>
> Changes in v3:
> - change "crtc->fb" to "crtc->primary-fb"
> Adviced by Daniel Vetter
> - init cursor plane with universal api, remove unnecessary cursor set,move
>
> Changes in v4:
> Adviced by David Herrmann
> - remove drm_platform_*() usage, use register drm device directly.
> Adviced by Rob Clark
> - remove special mmap ioctl, do userspace mmap with normal mmap() or mmap offset
>
> Changes in v5:
> Adviced by Arnd Bergmann
> - doing DMA start with a 32-bit masks with dma_mask and dma_coherent_mark
> - fix some incorrect dependencies.
> Adviced by Boris BREZILLON
> - fix some mistake and bugs.
> Adviced by Daniel Vetter
> - drop all special ioctl and use generic kms ioctl instead.
> Adviced by Rob Clark
> - use unlocked api for drm_fb_helper_restore_fbdev_mode.
> - remove unused rockchip_gem_prime_import_sg_table.
>
> Changes in v6:
> - set gem buffer pitch 64 bytes align, needed by mali gpu.
> Adviced by Daniel Kurtz
> - fix some mistake, bugs, remove unused define, more better code style etc.
> - use clk_prepare()/unprepare() at probe()/remove() and clk_enable()/disable()
> at runtime instead of clk_prepare_enable().
> - provide a help function from vop for encoder to do mode config, instead of
> using drm_diaplay_mode private method.
> - change vop mode_set timing to make it more safely.
>
> Changes in v7:
> - fix memory leakage problem
>
> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/rockchip/Kconfig | 17 +
> drivers/gpu/drm/rockchip/Makefile | 8 +
> drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 516 +++++++++
> drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 61 ++
> drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 ++++
> drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
> drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 230 ++++
> drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 20 +
> drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 345 ++++++
> drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 55 +
> drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1422 +++++++++++++++++++++++++
> drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 196 ++++
> 14 files changed, 3102 insertions(+)
> create mode 100644 drivers/gpu/drm/rockchip/Kconfig
> create mode 100644 drivers/gpu/drm/rockchip/Makefile
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.h
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.c
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.h
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index b066bb3..7c4c3c6 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -171,6 +171,8 @@ config DRM_SAVAGE
>
> source "drivers/gpu/drm/exynos/Kconfig"
>
> +source "drivers/gpu/drm/rockchip/Kconfig"
> +
> source "drivers/gpu/drm/vmwgfx/Kconfig"
>
> source "drivers/gpu/drm/gma500/Kconfig"
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 4a55d59..d03387a 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -52,6 +52,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/
> obj-$(CONFIG_DRM_VIA) +=via/
> obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
> obj-$(CONFIG_DRM_EXYNOS) +=exynos/
> +obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
> obj-$(CONFIG_DRM_GMA500) += gma500/
> obj-$(CONFIG_DRM_UDL) += udl/
> obj-$(CONFIG_DRM_AST) += ast/
> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
> new file mode 100644
> index 0000000..87255f7
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/Kconfig
> @@ -0,0 +1,17 @@
> +config DRM_ROCKCHIP
> + tristate "DRM Support for Rockchip"
> + depends on DRM && ROCKCHIP_IOMMU && ARM_DMA_USE_IOMMU && IOMMU_API
> + select DRM_KMS_HELPER
> + select DRM_KMS_FB_HELPER
> + select DRM_PANEL
> + select FB_CFB_FILLRECT
> + select FB_CFB_COPYAREA
> + select FB_CFB_IMAGEBLIT
> + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
> + select VIDEOMODE_HELPERS
> + help
> + Choose this option if you have a Rockchip soc chipset.
> + This driver provides kernel mode setting and buffer
> + management to userspace. This driver does not provides
> + 2D or 3D acceleration; acceleration is performed by other
> + IP found on the SoC.
> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
> new file mode 100644
> index 0000000..b3a5193
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/Makefile
> @@ -0,0 +1,8 @@
> +#
> +# Makefile for the drm device driver. This driver provides support for the
> +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
> +
> +rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o rockchip_drm_fbdev.o \
> + rockchip_drm_gem.o rockchip_drm_vop.o
> +
> +obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
> new file mode 100644
> index 0000000..a571acc
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
> @@ -0,0 +1,516 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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 <asm/dma-iommu.h>
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/of_graph.h>
> +#include <linux/component.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
> +
> +/*
> + * 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 dma_iommu_mapping *mapping = drm_dev->dev->archdata.mapping;
> + int ret;
> +
> + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
> + if (ret)
> + return ret;
> +
> + dma_set_max_seg_size(dev, 0xffffffffu);
Perhaps:
dma_set_max_seg_size(dev, DMA_BIT_MASK(32));
> +
> + return arm_iommu_attach_device(dev, mapping);
> +}
> +
> +void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
> + struct device *dev)
> +{
> + arm_iommu_detach_device(drm_dev->dev);
> +}
> +
> +static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags)
> +{
> + struct rockchip_drm_private *private;
> + struct dma_iommu_mapping *mapping;
> + struct device *dev = drm_dev->dev;
> + int ret;
> +
> + private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
> + if (!private)
> + return -ENOMEM;
> +
> + drm_dev->dev_private = private;
> +
> + drm_mode_config_init(drm_dev);
> +
> + rockchip_drm_mode_config_init(drm_dev);
> +
> + dev->dma_parms = devm_kzalloc(dev, sizeof(*dev->dma_parms),
> + GFP_KERNEL);
> + if (!dev->dma_parms) {
> + ret = -ENOMEM;
> + goto err_config_cleanup;
> + }
> +
> + /* TODO(djkurtz): fetch the mapping start/size from somewhere */
> + mapping = arm_iommu_create_mapping(&platform_bus_type, 0x10000000,
> + SZ_1G);
Can we make this bigger, and aligned to the requested size?
I find it makes it easier to debug that way.
How about:
mapping = arm_iommu_create_mapping(&platform_bus_type, 0x80000000, SZ_2G);
In fact, is there any reason why we do not just make this a 4GB mapping starting
at 0x00000000? Is that even possible?
> + if (IS_ERR(mapping)) {
> + ret = PTR_ERR(mapping);
> + goto err_config_cleanup;
> + }
> +
> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
> + if (ret)
> + goto err_release_mapping;
> +
> + dma_set_max_seg_size(dev, 0xffffffffu);
see above.
> +
> + ret = arm_iommu_attach_device(dev, mapping);
> + if (ret)
> + goto err_release_mapping;
> +
> + /* Try to bind all sub drivers. */
> + ret = component_bind_all(dev, drm_dev);
> + if (ret)
> + goto err_detach_device;
> +
> + /* 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;
> +
> + /*
> + * with vblank_disable_allowed = true, vblank interrupt will be disabled
> + * by drm timer once a current process gives up ownership of
> + * vblank event.(after drm_vblank_put function is called)
> + */
> + drm_dev->vblank_disable_allowed = true;
> +
> + ret = drm_vblank_init(drm_dev, ROCKCHIP_MAX_CRTC);
> + if (ret)
> + goto err_kms_helper_poll_fini;
> +
> + rockchip_drm_fbdev_init(drm_dev);
> +
> + /* force connectors detection */
> + drm_helper_hpd_irq_event(drm_dev);
> +
> + return 0;
> +
> +err_kms_helper_poll_fini:
> + drm_kms_helper_poll_fini(drm_dev);
> + component_unbind_all(dev, drm_dev);
> +err_detach_device:
> + arm_iommu_detach_device(dev);
> +err_release_mapping:
> + arm_iommu_release_mapping(dev->archdata.mapping);
> +err_config_cleanup:
> + drm_mode_config_cleanup(drm_dev);
> + drm_dev->dev_private = NULL;
> + return ret;
> +}
> +
> +static int rockchip_drm_unload(struct drm_device *drm_dev)
> +{
> + struct device *dev = drm_dev->dev;
> +
> + drm_kms_helper_poll_fini(drm_dev);
> + component_unbind_all(dev, drm_dev);
> + arm_iommu_detach_device(dev);
> + arm_iommu_release_mapping(dev->archdata.mapping);
> + drm_mode_config_cleanup(drm_dev);
> + drm_dev->dev_private = NULL;
> +
> + return 0;
> +}
> +
> +void rockchip_drm_lastclose(struct drm_device *dev)
> +{
> + struct rockchip_drm_private *priv = dev->dev_private;
> +
> + if (priv->fb_helper)
> + drm_fb_helper_restore_fbdev_mode_unlocked(priv->fb_helper);
> +}
> +
> +static const struct file_operations rockchip_drm_driver_fops = {
> + .owner = THIS_MODULE,
> + .open = drm_open,
> + .mmap = rockchip_drm_gem_mmap,
> + .poll = drm_poll,
> + .read = drm_read,
> + .unlocked_ioctl = drm_ioctl,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl = drm_compat_ioctl,
> +#endif
> + .release = drm_release,
> +};
> +
> +const struct vm_operations_struct rockchip_drm_vm_ops = {
> + .open = drm_gem_vm_open,
> + .close = drm_gem_vm_close,
> +};
> +
> +static struct drm_driver rockchip_drm_driver = {
> + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
> + .load = rockchip_drm_load,
> + .unload = rockchip_drm_unload,
> + .lastclose = rockchip_drm_lastclose,
> + .get_vblank_counter = drm_vblank_count,
> + .enable_vblank = rockchip_drm_crtc_enable_vblank,
> + .disable_vblank = rockchip_drm_crtc_disable_vblank,
> + .gem_vm_ops = &rockchip_drm_vm_ops,
> + .gem_free_object = 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_prime_mmap,
> + .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 int rockchip_drm_suspend(struct drm_device *dev, pm_message_t state)
> +{
> + struct drm_connector *connector;
> +
> + drm_modeset_lock_all(dev);
> + list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
> + int old_dpms = connector->dpms;
> +
> + if (connector->funcs->dpms)
> + connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
> +
> + /* Set the old mode back to the connector for resume */
> + connector->dpms = old_dpms;
> + }
> + drm_modeset_unlock_all(dev);
> +
> + return 0;
> +}
> +
> +static int rockchip_drm_resume(struct drm_device *dev)
> +{
> + struct drm_connector *connector;
> +
> + drm_modeset_lock_all(dev);
> + list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
> + if (connector->funcs->dpms)
> + connector->funcs->dpms(connector, connector->dpms);
> + }
> + drm_modeset_unlock_all(dev);
> +
> + drm_helper_resume_force_mode(dev);
> +
> + return 0;
> +}
> +
> +static int rockchip_drm_sys_suspend(struct device *dev)
> +{
> + struct drm_device *drm_dev = dev_get_drvdata(dev);
> + pm_message_t message;
> +
> + if (pm_runtime_suspended(dev))
> + return 0;
> +
> + message.event = PM_EVENT_SUSPEND;
> +
> + return rockchip_drm_suspend(drm_dev, message);
> +}
> +
> +static int rockchip_drm_sys_resume(struct device *dev)
> +{
> + struct drm_device *drm_dev = dev_get_drvdata(dev);
> +
> + if (!pm_runtime_suspended(dev))
> + return 0;
> +
> + return rockchip_drm_resume(drm_dev);
> +}
> +#endif
> +
> +static const struct dev_pm_ops rockchip_drm_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
> + rockchip_drm_sys_resume)
> +};
> +
> +int rockchip_drm_add_crtc(struct drm_device *drm, struct drm_crtc *crtc,
> + struct device_node *np)
> +{
> + struct rockchip_drm_private *priv = drm->dev_private;
> + struct device_node *port;
> + int pipe;
> +
> + if (priv->num_pipe >= ROCKCHIP_MAX_CRTC)
> + return -EINVAL;
> +
> + port = of_get_child_by_name(np, "port");
> + if (!port) {
> + dev_err(drm->dev, "no port node found in %s\n",
> + np->full_name);
> + return -ENXIO;
> + }
> + pipe = priv->num_pipe++;
> + crtc->port = port;
> +
> + priv->crtc[pipe] = crtc;
> +
> + return pipe;
> +}
> +
> +void rockchip_drm_remove_crtc(struct drm_device *drm, int pipe)
> +{
> + struct rockchip_drm_private *priv = drm->dev_private;
> +
> + priv->num_pipe--;
> + of_node_put(priv->crtc[pipe]->port);
> + priv->crtc[pipe] = NULL;
> +}
> +
> +struct drm_crtc *rockchip_drm_find_crtc(struct drm_device *drm, int pipe)
> +{
> + struct rockchip_drm_private *priv = drm->dev_private;
> +
> + if (pipe < ROCKCHIP_MAX_CRTC && priv->crtc[pipe])
> + return priv->crtc[pipe];
> +
> + return NULL;
> +}
> +
> +/*
> + * @node: device tree node containing encoder input ports
> + * @encoder: drm_encoder
> + */
> +int rockchip_drm_encoder_get_mux_id(struct device_node *node,
> + struct drm_encoder *encoder)
> +{
> + struct device_node *ep = NULL;
> + struct drm_crtc *crtc = encoder->crtc;
> + struct of_endpoint endpoint;
> + struct device_node *port;
> + int ret;
> +
> + if (!node || !crtc)
> + return -EINVAL;
> +
> + do {
> + ep = of_graph_get_next_endpoint(node, ep);
> + if (!ep)
> + break;
> +
> + port = of_graph_get_remote_port(ep);
> + of_node_put(port);
> + if (port == crtc->port) {
> + ret = of_graph_parse_endpoint(ep, &endpoint);
> + return ret ?: endpoint.id;
> + }
> + } while (ep);
> +
> + return -EINVAL;
> +}
> +
> +static int compare_of(struct device *dev, void *data)
> +{
> + struct device_node *np = data;
> +
> + return dev->of_node == np;
> +}
> +
> +static void rockchip_add_endpoints(struct device *dev,
> + struct component_match **match,
> + struct device_node *port)
> +{
> + struct device_node *ep, *remote;
> +
> + for_each_child_of_node(port, ep) {
> + remote = of_graph_get_remote_port_parent(ep);
> + if (!remote || !of_device_is_available(remote)) {
> + of_node_put(remote);
> + continue;
> + } else if (!of_device_is_available(remote->parent)) {
> + dev_warn(dev, "parent device of %s is not available\n",
> + remote->full_name);
> + of_node_put(remote);
> + continue;
> + }
> +
> + component_match_add(dev, match, compare_of, remote);
> + of_node_put(remote);
> + }
> +}
> +
> +static int rockchip_drm_bind(struct device *dev)
> +{
> + struct drm_device *drm;
> + int ret;
> +
> + drm = drm_dev_alloc(&rockchip_drm_driver, dev);
> + if (!drm)
> + return -ENOMEM;
> +
> + ret = drm_dev_set_unique(drm, "%s", dev_name(dev));
> + if (ret)
> + goto err_free;
> +
> + ret = drm_dev_register(drm, 0);
> + if (ret)
> + goto err_free;
> +
> + dev_set_drvdata(dev, drm);
> +
> + return 0;
> +
> +err_free:
> + drm_dev_unref(drm);
> + return ret;
> +}
> +
> +static void rockchip_drm_unbind(struct device *dev)
> +{
> + struct drm_device *drm = dev_get_drvdata(dev);
> +
> + drm_dev_unregister(drm);
> + drm_dev_unref(drm);
> +}
> +
> +static const struct component_master_ops rockchip_drm_ops = {
> + .bind = rockchip_drm_bind,
> + .unbind = rockchip_drm_unbind,
> +};
> +
> +static int rockchip_drm_platform_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct component_match *match = NULL;
> + struct device_node *np = dev->of_node;
> + struct device_node *port;
> + int i;
> + int ret;
> +
> + if (!np)
> + return -ENODEV;
> + /*
> + * Bind the crtc ports first, so that
> + * drm_of_find_possible_crtcs called from encoder .bind callbacks
> + * works as expected.
> + */
> + for (i = 0;; i++) {
> + port = of_parse_phandle(np, "ports", i);
> + if (!port)
> + break;
> +
> + if (!of_device_is_available(port->parent)) {
> + of_node_put(port);
> + continue;
> + }
> +
> + component_match_add(dev, &match, compare_of, port->parent);
> + of_node_put(port);
> + }
> +
> + if (i == 0) {
> + dev_err(dev, "missing 'ports' property\n");
> + return -ENODEV;
> + }
> + /*
> + * For each bound crtc, bind the encoders attached to its
> + * remote endpoint.
> + */
> + for (i = 0;; i++) {
> + port = of_parse_phandle(np, "ports", i);
> + if (!port)
> + break;
> +
> + if (!of_device_is_available(port->parent)) {
> + of_node_put(port);
> + continue;
> + }
> +
> + rockchip_add_endpoints(dev, &match, port);
> + of_node_put(port);
> + }
> +
> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
> + if (ret)
> + return ret;
This is in drm_load() now.
> +
> + 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 = {
> + .owner = THIS_MODULE,
> + .name = "rockchip-drm",
> + .of_match_table = rockchip_drm_dt_ids,
> + .pm = &rockchip_drm_pm_ops,
> + },
> +};
> +
> +module_platform_driver(rockchip_drm_platform_driver);
> +
> +MODULE_AUTHOR("Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>");
> +MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> new file mode 100644
> index 0000000..98094ecf
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> @@ -0,0 +1,61 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * based on exynos_drm_drv.h
> + *
> + * 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.
> + */
> +
> +#ifndef _ROCKCHIP_DRM_DRV_H
> +#define _ROCKCHIP_DRM_DRV_H
> +
> +#include <linux/module.h>
> +#include <linux/component.h>
> +
> +#define ROCKCHIP_MAX_FB_BUFFER 3
> +#define ROCKCHIP_MAX_CONNECTOR 2
> +#define ROCKCHIP_MAX_CRTC 2
> +
> +struct drm_device;
> +struct drm_connector;
> +
> +/*
> + * Rockchip drm private structure.
> + *
> + * @crtc: array of enabled CRTCs, used to map from "pipe" to drm_crtc.
> + * @num_pipe: number of pipes for this device.
> + */
> +struct rockchip_drm_private {
> + struct drm_fb_helper *fb_helper;
> + /*
> + * created crtc object would be contained at this array and
> + * this array is used to be aware of which crtc did it request vblank.
> + */
> + struct drm_crtc *crtc[ROCKCHIP_MAX_CRTC];
> +
> + unsigned int num_pipe;
> +};
> +
> +int rockchip_drm_add_crtc(struct drm_device *drm, struct drm_crtc *crtc,
> + struct device_node *port);
> +void rockchip_drm_remove_crtc(struct drm_device *drm, int pipe);
> +struct drm_crtc *rockchip_drm_find_crtc(struct drm_device *drm, int pipe);
> +int rockchip_drm_encoder_get_mux_id(struct device_node *node,
> + struct drm_encoder *encoder);
> +int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type);
> +int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe);
> +void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe);
> +int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
> + struct device *dev);
> +void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
> + struct device *dev);
> +
> +#endif /* _ROCKCHIP_DRM_DRV_H_ */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> new file mode 100644
> index 0000000..482f7b8
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> @@ -0,0 +1,201 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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 <linux/kernel.h>
> +#include <drm/drm.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +
> +#include "rockchip_drm_drv.h"
> +#include "rockchip_drm_gem.h"
> +
> +#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
> +
> +struct rockchip_drm_fb {
> + struct drm_framebuffer fb;
> + struct drm_gem_object *obj[ROCKCHIP_MAX_FB_BUFFER];
> +};
> +
> +struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
> + unsigned int plane)
> +{
> + struct rockchip_drm_fb *rk_fb = to_rockchip_fb(fb);
> +
> + if (plane >= ROCKCHIP_MAX_FB_BUFFER)
> + return NULL;
> +
> + return rk_fb->obj[plane];
> +}
> +
> +static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
> +{
> + struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
> + struct drm_gem_object *obj;
> + int i;
> +
> + for (i = 0; i < ROCKCHIP_MAX_FB_BUFFER; i++) {
> + obj = rockchip_fb->obj[i];
> + if (obj)
> + drm_gem_object_unreference_unlocked(obj);
> + }
> +
> + drm_framebuffer_cleanup(fb);
> + kfree(rockchip_fb);
> +}
> +
> +static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb,
> + struct drm_file *file_priv,
> + unsigned int *handle)
> +{
> + struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
> +
> + return drm_gem_handle_create(file_priv,
> + rockchip_fb->obj[0], handle);
> +}
> +
> +static struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
> + .destroy = rockchip_drm_fb_destroy,
> + .create_handle = rockchip_drm_fb_create_handle,
> +};
> +
> +static struct rockchip_drm_fb *
> +rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd,
> + struct drm_gem_object **obj, unsigned int num_planes)
> +{
> + struct rockchip_drm_fb *rockchip_fb;
> + int ret;
> + int i;
> +
> + rockchip_fb = kzalloc(sizeof(*rockchip_fb), GFP_KERNEL);
> + if (!rockchip_fb)
> + return ERR_PTR(-ENOMEM);
> +
> + drm_helper_mode_fill_fb_struct(&rockchip_fb->fb, mode_cmd);
> +
> + for (i = 0; i < num_planes; i++)
> + rockchip_fb->obj[i] = obj[i];
> +
> + ret = drm_framebuffer_init(dev, &rockchip_fb->fb,
> + &rockchip_drm_fb_funcs);
> + if (ret) {
> + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
> + ret);
> + kfree(rockchip_fb);
> + return ERR_PTR(ret);
> + }
> +
> + return rockchip_fb;
> +}
> +
> +static struct drm_framebuffer *
> +rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
> + struct drm_mode_fb_cmd2 *mode_cmd)
> +{
> + struct rockchip_drm_fb *rockchip_fb;
> + struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER];
> + struct drm_gem_object *obj;
> + unsigned int hsub;
> + unsigned int vsub;
> + int num_planes;
> + int ret;
> + int i;
> +
> + hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
> + vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
> + num_planes = min(drm_format_num_planes(mode_cmd->pixel_format),
> + ROCKCHIP_MAX_FB_BUFFER);
> +
> + for (i = 0; i < num_planes; i++) {
> + unsigned int width = mode_cmd->width / (i ? hsub : 1);
> + unsigned int height = mode_cmd->height / (i ? vsub : 1);
> + unsigned int min_size;
> +
> + obj = drm_gem_object_lookup(dev, file_priv,
> + mode_cmd->handles[i]);
> + if (!obj) {
> + dev_err(dev->dev, "Failed to lookup GEM object\n");
> + ret = -ENXIO;
> + goto err_gem_object_unreference;
> + }
> +
> + min_size = (height - 1) * mode_cmd->pitches[i] +
> + mode_cmd->offsets[i] +
> + width * drm_format_plane_cpp(mode_cmd->pixel_format, i);
> +
> + if (obj->size < min_size) {
> + drm_gem_object_unreference_unlocked(obj);
> + ret = -EINVAL;
> + goto err_gem_object_unreference;
> + }
> + objs[i] = obj;
> + }
> +
> + rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, objs, i);
> + if (IS_ERR(rockchip_fb)) {
> + ret = PTR_ERR(rockchip_fb);
> + goto err_gem_object_unreference;
> + }
> +
> + return &rockchip_fb->fb;
> +
> +err_gem_object_unreference:
> + for (i--; i >= 0; i--)
> + drm_gem_object_unreference_unlocked(objs[i]);
> + return ERR_PTR(ret);
> +}
> +
> +static void rockchip_drm_output_poll_changed(struct drm_device *dev)
> +{
> + struct rockchip_drm_private *private = dev->dev_private;
> + struct drm_fb_helper *fb_helper = private->fb_helper;
> +
> + if (fb_helper)
> + drm_fb_helper_hotplug_event(fb_helper);
> +}
> +
> +static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
> + .fb_create = rockchip_user_fb_create,
> + .output_poll_changed = rockchip_drm_output_poll_changed,
> +};
> +
> +struct drm_framebuffer *
> +rockchip_drm_framebuffer_init(struct drm_device *dev,
> + struct drm_mode_fb_cmd2 *mode_cmd,
> + struct drm_gem_object *obj)
> +{
> + struct rockchip_drm_fb *rockchip_fb;
> +
> + rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1);
> + if (IS_ERR(rockchip_fb))
> + return NULL;
> +
> + return &rockchip_fb->fb;
> +}
> +
> +void rockchip_drm_mode_config_init(struct drm_device *dev)
> +{
> + dev->mode_config.min_width = 0;
> + dev->mode_config.min_height = 0;
> +
> + /*
> + * set max width and height as default value(4096x4096).
> + * this value would be used to check framebuffer size limitation
> + * at drm_mode_addfb().
> + */
> + dev->mode_config.max_width = 4096;
> + dev->mode_config.max_height = 4096;
> +
> + dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
> +}
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
> new file mode 100644
> index 0000000..09574d4
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
> @@ -0,0 +1,28 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#ifndef _ROCKCHIP_DRM_FB_H
> +#define _ROCKCHIP_DRM_FB_H
> +
> +struct drm_framebuffer *
> +rockchip_drm_framebuffer_init(struct drm_device *dev,
> + struct drm_mode_fb_cmd2 *mode_cmd,
> + struct drm_gem_object *obj);
> +void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
> +
> +void rockchip_drm_mode_config_init(struct drm_device *dev);
> +
> +struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
> + unsigned int plane);
> +#endif /* _ROCKCHIP_DRM_FB_H */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
> new file mode 100644
> index 0000000..ae563f5
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
> @@ -0,0 +1,230 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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/drm.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +
> +#include "rockchip_drm_drv.h"
> +#include "rockchip_drm_gem.h"
> +#include "rockchip_drm_fb.h"
> +
> +#define PREFERRED_BPP 32
> +#define to_rockchip_fbdev(x) container_of(x, struct rockchip_fbdev, helper)
> +
> +struct rockchip_fbdev {
> + struct drm_fb_helper helper;
> + struct drm_gem_object *bo;
> +};
> +
> +static int rockchip_fbdev_mmap(struct fb_info *info,
> + struct vm_area_struct *vma)
> +{
> + struct drm_fb_helper *helper = info->par;
> + struct rockchip_fbdev *fbdev = to_rockchip_fbdev(helper);
> +
> + return rockchip_gem_mmap(fbdev->bo, vma);
> +}
> +
> +static struct fb_ops rockchip_drm_fbdev_ops = {
> + .owner = THIS_MODULE,
> + .fb_mmap = rockchip_fbdev_mmap,
> + .fb_fillrect = cfb_fillrect,
> + .fb_copyarea = cfb_copyarea,
> + .fb_imageblit = cfb_imageblit,
> + .fb_check_var = drm_fb_helper_check_var,
> + .fb_set_par = drm_fb_helper_set_par,
> + .fb_blank = drm_fb_helper_blank,
> + .fb_pan_display = drm_fb_helper_pan_display,
> + .fb_setcmap = drm_fb_helper_setcmap,
> +};
> +
> +static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
> + struct drm_fb_helper_surface_size *sizes)
> +{
> + struct rockchip_fbdev *fbdev = to_rockchip_fbdev(helper);
> + struct drm_mode_fb_cmd2 mode_cmd = { 0 };
> + struct drm_device *dev = helper->dev;
> + struct rockchip_gem_object *rk_obj;
> + struct drm_framebuffer *fb;
> + unsigned int bytes_per_pixel;
> + unsigned long offset;
> + struct fb_info *fbi;
> + size_t size;
> + int ret;
> +
> + bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
> +
> + mode_cmd.width = sizes->surface_width;
> + mode_cmd.height = sizes->surface_height;
> + mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
> + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
> + sizes->surface_depth);
> +
> + size = mode_cmd.pitches[0] * mode_cmd.height;
> +
> + rk_obj = rockchip_gem_create_object(dev, size);
> + if (IS_ERR(rk_obj))
> + return -ENOMEM;
> +
> + fbdev->bo = &rk_obj->base;
> +
> + fbi = framebuffer_alloc(0, dev->dev);
> + if (!fbi) {
> + dev_err(dev->dev, "Failed to allocate framebuffer info.\n");
> + ret = -ENOMEM;
> + goto err_rockchip_gem_free_object;
> + }
> +
> + helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd, fbdev->bo);
> + if (IS_ERR(helper->fb)) {
> + dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
> + ret = PTR_ERR(helper->fb);
> + goto err_framebuffer_release;
> + }
> +
> + helper->fbdev = fbi;
> +
> + fbi->par = helper;
> + fbi->flags = FBINFO_FLAG_DEFAULT;
> + fbi->fbops = &rockchip_drm_fbdev_ops;
> +
> + ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
> + if (ret) {
> + dev_err(dev->dev, "Failed to allocate color map.\n");
> + goto err_drm_framebuffer_unref;
> + }
> +
> + fb = helper->fb;
> + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
> + drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
> +
> + offset = fbi->var.xoffset * bytes_per_pixel;
> + offset += fbi->var.yoffset * fb->pitches[0];
> +
> + dev->mode_config.fb_base = 0;
> + fbi->screen_base = rk_obj->kvaddr + offset;
> + fbi->screen_size = rk_obj->base.size;
> + fbi->fix.smem_len = rk_obj->base.size;
> +
> + DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%d\n",
> + fb->width, fb->height, fb->depth, rk_obj->kvaddr,
> + offset, size);
> + return 0;
> +
> +err_drm_framebuffer_unref:
> + drm_framebuffer_unreference(helper->fb);
> +err_framebuffer_release:
> + framebuffer_release(fbi);
> +err_rockchip_gem_free_object:
> + rockchip_gem_free_object(&rk_obj->base);
> + return ret;
> +}
> +
> +static struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
> + .fb_probe = rockchip_drm_fbdev_create,
> +};
> +
> +int rockchip_drm_fbdev_init(struct drm_device *dev)
> +{
> + struct rockchip_drm_private *private = dev->dev_private;
> + struct rockchip_fbdev *fbdev;
> + struct drm_fb_helper *helper;
> + unsigned int num_crtc;
> + int ret;
> +
> + if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
> + return -EINVAL;
> +
> + if (private->fb_helper) {
> + DRM_ERROR("no allow to reinit fbdev\n");
> + return -EINVAL;
> + }
> +
> + num_crtc = dev->mode_config.num_crtc;
> +
> + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
Can you just embed rockchip_fbdev in struct rockchip_drm_private...
Or, just add its two fields to rockchip_drm_private:
struct drm_fb_helper fbdev_helper;
struct drm_gem_object *fbdev_bo;
It would eliminate some of the complexity in _fbdev_init, and especially in
_fbdev_fini.
> + if (!fbdev)
> + return -ENOMEM;
> +
> + fbdev->helper.funcs = &rockchip_drm_fb_helper_funcs;
> + helper = &fbdev->helper;
> +
> + ret = drm_fb_helper_init(dev, helper, num_crtc, ROCKCHIP_MAX_CONNECTOR);
> + if (ret < 0) {
> + dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
> + goto err_free;
> + }
> +
> + ret = drm_fb_helper_single_add_all_connectors(helper);
> + if (ret < 0) {
> + dev_err(dev->dev, "Failed to add connectors.\n");
> + goto err_drm_fb_helper_fini;
> + }
> +
> + /* disable all the possible outputs/crtcs before entering KMS mode */
> + drm_helper_disable_unused_functions(dev);
> +
> + ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
> + if (ret < 0) {
> + dev_err(dev->dev, "Failed to set initial hw configuration.\n");
> + goto err_drm_fb_helper_fini;
> + }
> +
> + private->fb_helper = helper;
> +
> + return 0;
> +
> +err_drm_fb_helper_fini:
> + drm_fb_helper_fini(helper);
> +err_free:
> + kfree(fbdev);
> + return ret;
> +}
> +
> +void rockchip_drm_fbdev_fini(struct drm_device *dev)
> +{
> + struct rockchip_drm_private *private = dev->dev_private;
> + struct drm_fb_helper *helper;
> + struct rockchip_fbdev *fbdev;
> +
> + if (!private || !private->fb_helper)
> + return;
Can these ever be NULL here?
> +
> + helper = private->fb_helper;
> + fbdev = to_rockchip_fbdev(helper);
> +
> + if (helper->fbdev) {
> + struct fb_info *info;
> + int ret;
> +
> + info = helper->fbdev;
> + ret = unregister_framebuffer(info);
> + if (ret < 0)
> + DRM_DEBUG_KMS("failed unregister_framebuffer()\n");
> +
> + if (info->cmap.len)
> + fb_dealloc_cmap(&info->cmap);
> +
> + framebuffer_release(info);
> + }
> +
> + if (helper->fb)
> + drm_framebuffer_unreference(helper->fb);
> +
> + drm_fb_helper_fini(helper);
> + kfree(fbdev);
> + private->fb_helper = NULL;
> +}
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
> new file mode 100644
> index 0000000..5edcf6a
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
> @@ -0,0 +1,20 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#ifndef _ROCKCHIP_DRM_FBDEV_H
> +#define _ROCKCHIP_DRM_FBDEV_H
> +
> +int rockchip_drm_fbdev_init(struct drm_device *dev);
> +
> +#endif /* _ROCKCHIP_DRM_FBDEV_H */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
> new file mode 100644
> index 0000000..5563dff
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
> @@ -0,0 +1,345 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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/drm.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_vma_manager.h>
> +
> +#include <linux/anon_inodes.h>
> +#include <linux/dma-attrs.h>
> +
> +#include "rockchip_drm_drv.h"
> +#include "rockchip_drm_gem.h"
> +
> +static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj)
> +{
> + struct drm_gem_object *obj = &rk_obj->base;
> + struct drm_device *drm = obj->dev;
> +
> + init_dma_attrs(&rk_obj->dma_attrs);
> + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &rk_obj->dma_attrs);
> +
> + /* TODO(djkurtz): Use DMA_ATTR_NO_KERNEL_MAPPING except for fbdev */
> + rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
> + &rk_obj->dma_addr, GFP_KERNEL,
> + &rk_obj->dma_attrs);
> + if (IS_ERR(rk_obj->kvaddr)) {
> + int ret = PTR_ERR(rk_obj->kvaddr);
> +
> + DRM_ERROR("failed to allocate %#x byte dma buffer, %d",
> + obj->size, ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void rockchip_gem_free_buf(struct rockchip_gem_object *rk_obj)
> +{
> + struct drm_gem_object *obj = &rk_obj->base;
> + struct drm_device *drm = obj->dev;
> +
> + dma_free_attrs(drm->dev, obj->size, rk_obj->kvaddr, rk_obj->dma_addr,
> + &rk_obj->dma_attrs);
> +}
> +
> +/* drm driver mmap file operations */
> +int rockchip_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + struct drm_file *priv = filp->private_data;
> + struct drm_device *dev = priv->minor->dev;
> + struct drm_gem_object *obj;
> + struct drm_vma_offset_node *node;
> + int ret;
> +
> + if (drm_device_is_unplugged(dev))
> + return -ENODEV;
> +
> + mutex_lock(&dev->struct_mutex);
> +
> + node = drm_vma_offset_exact_lookup(dev->vma_offset_manager,
> + vma->vm_pgoff,
> + vma_pages(vma));
> + if (!node) {
> + mutex_unlock(&dev->struct_mutex);
> + DRM_ERROR("failed to find vma node.\n");
> + return -EINVAL;
> + } else if (!drm_vma_node_is_allowed(node, filp)) {
> + mutex_unlock(&dev->struct_mutex);
> + return -EACCES;
> + }
> +
> + obj = container_of(node, struct drm_gem_object, vma_node);
> + ret = rockchip_gem_mmap(obj, vma);
> +
> + mutex_unlock(&dev->struct_mutex);
> +
> + return ret;
> +}
> +
> +int rockchip_drm_gem_mmap_buffer(struct file *filp,
> + struct vm_area_struct *vma)
> +{
> + struct drm_gem_object *obj = filp->private_data;
> +
> + return rockchip_gem_mmap(obj, vma);
> +}
> +
> +static const struct file_operations rockchip_drm_gem_fops = {
> + .mmap = rockchip_drm_gem_mmap_buffer,
> +};
> +
> +struct rockchip_gem_object *
> + rockchip_gem_create_object(struct drm_device *drm, unsigned int size)
> +{
> + struct rockchip_gem_object *rk_obj;
> + struct drm_gem_object *obj;
> + struct file *filp;
> + int ret;
> +
> + size = round_up(size, PAGE_SIZE);
> +
> + rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
> + if (!rk_obj)
> + return ERR_PTR(-ENOMEM);
> +
> + obj = &rk_obj->base;
> +
> + drm_gem_private_object_init(drm, obj, size);
> +
> + filp = anon_inode_getfile("rockchip_gem", &rockchip_drm_gem_fops,
> + obj, 0);
> + if (IS_ERR(filp)) {
> + DRM_ERROR("failed to create anon file object.\n");
> + ret = PTR_ERR(filp);
> + goto err_free_rk_obj;
> + }
> + filp->f_mode = FMODE_READ | FMODE_WRITE;
> + obj->filp = filp;
I think this chunk goes away now, if we do not have custom gem mmap ioctl? an
Along with rockchip_drm_gem_fops and rockchip_drm_gem_mmap_buffer.
> +
> + ret = drm_gem_create_mmap_offset(obj);
> + if (ret)
> + goto err_free_obj;
> +
> + ret = rockchip_gem_alloc_buf(rk_obj);
> + if (ret)
> + goto err_free_mmap_offset;
> +
> + return rk_obj;
> +
> +err_free_mmap_offset:
> + drm_gem_free_mmap_offset(obj);
> +err_free_obj:
> + drm_gem_object_release(obj);
> +err_free_rk_obj:
> + kfree(rk_obj);
> + return ERR_PTR(ret);
> +}
> +
> +/*
> + * rockchip_gem_free_object - (struct drm_driver)->gem_free_object callback
> + * function
> + */
> +void rockchip_gem_free_object(struct drm_gem_object *obj)
> +{
> + struct rockchip_gem_object *rk_obj;
> +
> + drm_gem_free_mmap_offset(obj);
> +
> + rk_obj = to_rockchip_obj(obj);
> +
> + rockchip_gem_free_buf(rk_obj);
> +
> + drm_gem_object_release(obj);
> +
> + kfree(rk_obj);
> +}
> +
> +int rockchip_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
Nit: Can you move this function up just below rockchip_gem_free_buf.
That would put these three dma_*_attrs() wrapper functions in one place.
> +{
> + struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
> + struct drm_device *drm = obj->dev;
> + unsigned long vm_size;
> +
> + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
> + vm_size = vma->vm_end - vma->vm_start;
> +
> + if (vm_size > obj->size)
> + return -EINVAL;
> +
> + return dma_mmap_attrs(drm->dev, vma, rk_obj->kvaddr, rk_obj->dma_addr,
> + obj->size, &rk_obj->dma_attrs);
> +}
> +
> +/*
> + * rockchip_gem_create_with_handle - allocate an object with the given
> + * size and create a gem handle on it
> + *
> + * returns a struct rockchip_gem_object* on success or ERR_PTR values
> + * on failure.
> + */
> +static struct rockchip_gem_object *
> +rockchip_gem_create_with_handle(struct drm_file *file_priv,
> + struct drm_device *drm, unsigned int size,
> + unsigned int *handle)
> +{
> + struct rockchip_gem_object *rk_obj;
> + struct drm_gem_object *obj;
> + int ret;
> +
> + rk_obj = rockchip_gem_create_object(drm, size);
> + if (IS_ERR(rk_obj))
> + return NULL;
Should this be:
return ERR_CAST(rk_obj);
> +
> + obj = &rk_obj->base;
> +
> + /*
> + * allocate a id of idr table where the obj is registered
> + * and handle has the id what user can see.
> + */
> + ret = drm_gem_handle_create(file_priv, obj, handle);
> + if (ret)
> + goto err_handle_create;
> +
> + /* drop reference from allocate - handle holds it now. */
> + drm_gem_object_unreference_unlocked(obj);
> +
> + return rk_obj;
> +
> +err_handle_create:
> + rockchip_gem_free_object(obj);
> +
> + return ERR_PTR(ret);
> +}
> +
> +int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
> + struct drm_device *dev, uint32_t handle,
> + uint64_t *offset)
> +{
> + struct drm_gem_object *obj;
> + int ret = 0;
Nit: No = 0;
(I am a fan of letting the compiler tell us if we forget to initialize a
return value in goto/error paths)
> +
> + mutex_lock(&dev->struct_mutex);
> +
> + /*
> + * get offset of memory allocated for drm framebuffer.
> + * - this callback would be called by user application
> + * with DRM_IOCTL_MODE_MAP_DUMB command.
> + */
Just delete this comment that was copied from exynos.
> +
> + obj = drm_gem_object_lookup(dev, file_priv, handle);
> + if (!obj) {
> + DRM_ERROR("failed to lookup gem object.\n");
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + ret = drm_gem_create_mmap_offset(obj);
> + if (ret)
> + goto out;
> +
> + *offset = drm_vma_node_offset_addr(&obj->vma_node);
> + DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset);
Nit: print all 64 bits of offset
> +
> +out:
> + drm_gem_object_unreference(obj);
> +unlock:
> + mutex_unlock(&dev->struct_mutex);
> + return ret;
> +}
> +
> +/*
> + * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
> + * function
> + *
> + * This aligns the pitch and size arguments to the minimum required. wrap
> + * this into your own function if you need bigger alignment.
> + */
> +int rockchip_gem_dumb_create(struct drm_file *file_priv,
> + struct drm_device *dev,
> + struct drm_mode_create_dumb *args)
> +{
> + struct rockchip_gem_object *rk_obj;
> + int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> +
> + /*
> + * align to 64 bytes since Mali requires it.
> + */
> + min_pitch = ((min_pitch + 63) / 64) * 64;
ALIGN(min_pitch, 64);
> +
> + if (args->pitch < min_pitch)
> + args->pitch = min_pitch;
> +
> + if (args->size < args->pitch * args->height)
> + args->size = args->pitch * args->height;
> +
> + rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
> + &args->handle);
> +
> + return PTR_ERR_OR_ZERO(rk_obj);
> +}
> +
> +/*
> + * Allocate a sg_table for this GEM object.
> + * Note: Both the table's contents, and the sg_table itself must be freed by
> + * the caller.
> + * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
> + */
> +struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj)
> +{
> + struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
> + struct drm_device *drm = obj->dev;
> + struct sg_table *sgt = NULL;
Do not init to NULL
> + int ret;
> +
> + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
> + if (!sgt)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = dma_get_sgtable_attrs(drm->dev, sgt, rk_obj->kvaddr,
> + rk_obj->dma_addr, obj->size,
> + &rk_obj->dma_attrs);
> + if (ret) {
> + DRM_ERROR("failed to allocate sgt, %d\n", ret);
> + kfree(sgt);
> + return ERR_PTR(ret);
> + }
> +
> + return sgt;
> +}
> +
> +void *rockchip_gem_prime_vmap(struct drm_gem_object *obj)
> +{
> + struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
> +
> + return rk_obj->kvaddr;
> +}
> +
> +void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
> +{
> + /* Nothing to do */
> +}
> +
> +int rockchip_gem_prime_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma)
I don't think this function is correct. I think we should use dma_mmap_attrs()?
But, you can probably just remove it, and drop support for mmap'ing
exported dma_bufs for now.
> +{
> + struct drm_device *dev = obj->dev;
> + int ret;
> +
> + mutex_lock(&dev->struct_mutex);
> + ret = drm_gem_mmap_obj(obj, obj->size, vma);
> + mutex_unlock(&dev->struct_mutex);
> +
> + return ret;
> +}
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.h b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
> new file mode 100644
> index 0000000..e37b921
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
> @@ -0,0 +1,55 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#ifndef _ROCKCHIP_DRM_GEM_H
> +#define _ROCKCHIP_DRM_GEM_H
> +
> +#define to_rockchip_obj(x) container_of(x, struct rockchip_gem_object, base)
> +
> +struct rockchip_gem_object {
> + struct drm_gem_object base;
> + unsigned int flags;
> +
> + void *kvaddr;
> + dma_addr_t dma_addr;
> + struct dma_attrs dma_attrs;
> +};
> +
> +struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj);
> +struct drm_gem_object *
> +rockchip_gem_prime_import_sg_table(struct drm_device *dev, size_t size,
> + struct sg_table *sgt);
> +void *rockchip_gem_prime_vmap(struct drm_gem_object *obj);
> +void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
> +int rockchip_gem_prime_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma);
> +
> +/* drm driver mmap file operations */
> +int rockchip_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
> +
> +/* mmap a gem object to userspace. */
> +int rockchip_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
> +
> +struct rockchip_gem_object *
> + rockchip_gem_create_object(struct drm_device *drm, unsigned int size);
> +
> +void rockchip_gem_free_object(struct drm_gem_object *obj);
> +
> +int rockchip_gem_dumb_create(struct drm_file *file_priv,
> + struct drm_device *dev,
> + struct drm_mode_create_dumb *args);
> +int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
> + struct drm_device *dev, uint32_t handle,
> + uint64_t *offset);
> +#endif /* _ROCKCHIP_DRM_GEM_H */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> new file mode 100644
> index 0000000..fe9d2b0
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> @@ -0,0 +1,1422 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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/drm.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/component.h>
> +
> +#include <linux/reset.h>
> +#include <linux/iommu.h>
> +#include <linux/delay.h>
> +
> +#include <video/of_display_timing.h>
> +#include <video/of_videomode.h>
> +
> +#include "rockchip_drm_drv.h"
> +#include "rockchip_drm_fbdev.h"
> +#include "rockchip_drm_gem.h"
> +#include "rockchip_drm_fb.h"
> +#include "rockchip_drm_vop.h"
> +
> +#define VOP_DEFAULT_FRAMERATE 60
> +#define VOP_MAX_WIN_SUPPORT 5
> +#define VOP_DEFAULT_PRIMARY 0
> +#define VOP_DEFAULT_CURSOR 1
> +#define VOP_REG(off, _mask, s) \
> + {.offset = off, \
> + .mask = _mask, \
> + .shift = s,}
> +
> +#define __REG_SET(x, off, mask, shift, v) \
> + vop_mask_write(x, off, (mask) << shift, (v) << shift)
> +
> +#define REG_SET(x, base, reg, v) \
> + __REG_SET(x, base + reg.offset, reg.mask, reg.shift, v)
> +
> +#define VOP_WIN_SET(x, win, name, v) \
> + REG_SET(x, win->base, win->phy->name, v)
> +#define VOP_CTRL_SET(x, name, v) \
> + REG_SET(x, 0, (x)->data->ctrl->name, v)
> +
> +#define VOP_WIN_GET_YRGBADDR(ctx, win) \
> + vop_readl(ctx, win->base + win->phy->yrgb_mst.offset)
> +
> +#define to_vop_ctx(x) container_of(x, struct vop_context, crtc)
> +#define to_rockchip_plane(x) container_of(x, struct rockchip_plane, base)
> +
> +struct rockchip_plane {
> + int id;
> + struct drm_plane base;
> + const struct vop_win *win;
> +
> + uint32_t pending_yrgb_mst;
> + struct drm_framebuffer *front_fb;
> + struct drm_framebuffer *pending_fb;
> + bool enabled;
> +};
> +
> +struct vop_context {
> + struct device *dev;
> + struct drm_device *drm_dev;
> + struct drm_crtc crtc;
> + struct drm_pending_vblank_event *event;
> + struct vop_driver *drv;
> + unsigned int dpms;
> + unsigned int win_mask;
> + wait_queue_head_t wait_vsync_queue;
> + atomic_t wait_vsync_event;
> +
> + struct workqueue_struct *vsync_wq;
> + struct work_struct vsync_work;
> +
> + /* mutex vsync_ work */
> + struct mutex vsync_mutex;
> + bool vsync_work_pending;
> +
> + struct vop_driver_data *data;
> +
> + uint32_t *regsbak;
> + void __iomem *regs;
> +
> + /* physical map length of vop register */
> + uint32_t len;
> +
> + /* one time only one process allowed to config the register */
> + spinlock_t reg_lock;
> + /* lock vop irq reg */
> + spinlock_t irq_lock;
> +
> + unsigned int irq;
> +
> + /* vop AHP clk */
> + struct clk *hclk;
> + /* vop dclk */
> + struct clk *dclk;
> + /* vop share memory frequency */
> + struct clk *aclk;
> +
> + /* vop ahb reset */
> + struct reset_control *ahb_rst;
> + /* vop dclk reset */
> + struct reset_control *dclk_rst;
> + uint32_t pixclock;
Where is pixclock used?
> +
> + int pipe;
> + bool clk_on;
> +};
> +
> +enum vop_data_format {
> + VOP_FMT_ARGB8888 = 0,
> + VOP_FMT_RGB888,
> + VOP_FMT_RGB565,
> + VOP_FMT_YUV420SP = 4,
> + VOP_FMT_YUV422SP,
> + VOP_FMT_YUV444SP,
> +};
> +
> +struct vop_reg_data {
> + uint32_t offset;
> + uint32_t value;
> +};
> +
> +struct vop_reg {
> + uint32_t offset;
> + uint32_t shift;
> + uint32_t mask;
> +};
> +
> +struct vop_ctrl {
> + struct vop_reg standby;
> + struct vop_reg data_blank;
> + struct vop_reg gate_en;
> + struct vop_reg mmu_en;
> + struct vop_reg rgb_en;
> + struct vop_reg edp_en;
> + struct vop_reg hdmi_en;
> + struct vop_reg mipi_en;
> + struct vop_reg out_mode;
> + struct vop_reg dither_down;
> + struct vop_reg dither_up;
> + struct vop_reg pin_pol;
> +
> + struct vop_reg htotal_pw;
> + struct vop_reg hact_st_end;
> + struct vop_reg vtotal_pw;
> + struct vop_reg vact_st_end;
> + struct vop_reg hpost_st_end;
> + struct vop_reg vpost_st_end;
> +};
> +
> +struct vop_win_phy {
> + const uint32_t *data_formats;
> + uint32_t nformats;
> +
> + struct vop_reg enable;
> + struct vop_reg format;
> + struct vop_reg act_info;
> + struct vop_reg dsp_info;
> + struct vop_reg dsp_st;
> + struct vop_reg yrgb_mst;
> + struct vop_reg uv_mst;
> + struct vop_reg yrgb_vir;
> + struct vop_reg uv_vir;
> +
> + struct vop_reg dst_alpha_ctl;
> + struct vop_reg src_alpha_ctl;
> +};
> +
> +struct vop_win {
> + uint32_t base;
> + const struct vop_win_phy *phy;
> +};
> +
> +struct vop_driver_data {
> + const void *init_table;
> + int table_size;
> + const struct vop_ctrl *ctrl;
> + const struct vop_win *win[VOP_MAX_WIN_SUPPORT];
> +};
> +
> +static const uint32_t formats_01[] = {
> + DRM_FORMAT_XRGB8888,
> + DRM_FORMAT_ARGB8888,
> + DRM_FORMAT_RGB888,
> + DRM_FORMAT_RGB565,
> + DRM_FORMAT_NV12,
> + DRM_FORMAT_NV16,
> + DRM_FORMAT_NV24,
> +};
> +
> +static const uint32_t formats_234[] = {
> + DRM_FORMAT_XRGB8888,
> + DRM_FORMAT_ARGB8888,
> + DRM_FORMAT_RGB888,
> + DRM_FORMAT_RGB565,
> +};
> +
> +static const struct vop_win_phy win01_data = {
> + .data_formats = formats_01,
> + .nformats = ARRAY_SIZE(formats_01),
> + .enable = VOP_REG(WIN0_CTRL0, 0x1, 0),
> + .format = VOP_REG(WIN0_CTRL0, 0x7, 1),
> + .act_info = VOP_REG(WIN0_ACT_INFO, 0x1fff1fff, 0),
> + .dsp_info = VOP_REG(WIN0_DSP_INFO, 0x1fff1fff, 0),
> + .dsp_st = VOP_REG(WIN0_DSP_ST, 0x1fff1fff, 0),
> + .yrgb_mst = VOP_REG(WIN0_YRGB_MST, 0xffffffff, 0),
> + .uv_mst = VOP_REG(WIN0_CBR_MST, 0xffffffff, 0),
> + .yrgb_vir = VOP_REG(WIN0_VIR, 0x3fff, 0),
> + .uv_vir = VOP_REG(WIN0_VIR, 0x3fff, 16),
> + .src_alpha_ctl = VOP_REG(WIN0_SRC_ALPHA_CTRL, 0xff, 0),
> + .dst_alpha_ctl = VOP_REG(WIN0_DST_ALPHA_CTRL, 0xff, 0),
> +};
> +
> +static const struct vop_win_phy win23_data = {
> + .data_formats = formats_234,
> + .nformats = ARRAY_SIZE(formats_234),
> + .enable = VOP_REG(WIN2_CTRL0, 0x1, 0),
> + .format = VOP_REG(WIN2_CTRL0, 0x7, 1),
> + .dsp_info = VOP_REG(WIN2_DSP_INFO0, 0x0fff0fff, 0),
> + .dsp_st = VOP_REG(WIN2_DSP_ST0, 0x1fff1fff, 0),
> + .yrgb_mst = VOP_REG(WIN2_MST0, 0xffffffff, 0),
> + .yrgb_vir = VOP_REG(WIN2_VIR0_1, 0x1fff, 0),
> + .src_alpha_ctl = VOP_REG(WIN2_SRC_ALPHA_CTRL, 0xff, 0),
> + .dst_alpha_ctl = VOP_REG(WIN2_DST_ALPHA_CTRL, 0xff, 0),
> +};
> +
> +static const struct vop_win_phy cursor_data = {
> + .data_formats = formats_234,
> + .nformats = ARRAY_SIZE(formats_234),
> + .enable = VOP_REG(HWC_CTRL0, 0x1, 0),
> + .format = VOP_REG(HWC_CTRL0, 0x7, 1),
> + .dsp_st = VOP_REG(HWC_DSP_ST, 0x1fff1fff, 0),
> + .yrgb_mst = VOP_REG(HWC_MST, 0xffffffff, 0),
> +};
> +
> +static const struct vop_win win0 = {
> + .base = 0,
> + .phy = &win01_data,
> +};
> +
> +static const struct vop_win win1 = {
> + .base = 0x40,
> + .phy = &win01_data,
> +};
> +
> +static const struct vop_win win2 = {
> + .base = 0,
> + .phy = &win23_data,
> +};
> +
> +static const struct vop_win win3 = {
> + .base = 0x50,
> + .phy = &win23_data,
> +};
> +
> +static const struct vop_win win_cursor = {
> + .base = 0,
> + .phy = &cursor_data,
> +};
> +
> +static const struct vop_ctrl ctrl_data = {
> + .standby = VOP_REG(SYS_CTRL, 0x1, 22),
> + .gate_en = VOP_REG(SYS_CTRL, 0x1, 23),
> + .mmu_en = VOP_REG(SYS_CTRL, 0x1, 20),
> + .rgb_en = VOP_REG(SYS_CTRL, 0x1, 12),
> + .hdmi_en = VOP_REG(SYS_CTRL, 0x1, 13),
> + .edp_en = VOP_REG(SYS_CTRL, 0x1, 14),
> + .mipi_en = VOP_REG(SYS_CTRL, 0x1, 15),
> + .dither_down = VOP_REG(DSP_CTRL1, 0xf, 1),
> + .dither_up = VOP_REG(DSP_CTRL1, 0x1, 6),
> + .data_blank = VOP_REG(DSP_CTRL0, 0x1, 19),
> + .out_mode = VOP_REG(DSP_CTRL0, 0xf, 0),
> + .pin_pol = VOP_REG(DSP_CTRL0, 0xf, 4),
> + .htotal_pw = VOP_REG(DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
> + .hact_st_end = VOP_REG(DSP_HACT_ST_END, 0x1fff1fff, 0),
> + .vtotal_pw = VOP_REG(DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
> + .vact_st_end = VOP_REG(DSP_VACT_ST_END, 0x1fff1fff, 0),
> + .hpost_st_end = VOP_REG(POST_DSP_HACT_INFO, 0x1fff1fff, 0),
> + .vpost_st_end = VOP_REG(POST_DSP_VACT_INFO, 0x1fff1fff, 0),
> +};
> +
> +static const struct vop_reg_data vop_init_reg_table[] = {
> + {SYS_CTRL, 0x00801000},
> + {DSP_CTRL0, 0x00000000},
> + {WIN0_CTRL0, 0x00000080},
> + {WIN1_CTRL0, 0x00000080},
> +};
> +
> +static const struct vop_driver_data rockchip_rk3288_vop = {
> + .init_table = vop_init_reg_table,
> + .table_size = ARRAY_SIZE(vop_init_reg_table),
> + .ctrl = &ctrl_data,
> + .win[0] = &win0,
> + .win[1] = &win1,
> + .win[2] = &win2,
> + .win[3] = &win3,
> + .win[4] = &win_cursor,
> +};
> +
> +static const struct of_device_id vop_driver_dt_match[] = {
> + { .compatible = "rockchip,rk3288-vop",
> + .data = (void *)&rockchip_rk3288_vop },
> + {},
> +};
> +
> +static inline void vop_writel(struct vop_context *ctx,
> + uint32_t offset, uint32_t v)
> +{
> + writel(v, ctx->regs + offset);
TODO: I *think* the following may be a useful optimization:
When doing mode_set or update_plane, use writel_relaxed to write all of the
shadowed mode/window configuration registers, and just use writel() to write
vop_cfg_done. This will write all the configuration registers without barriers,
but still ensure that all such registers are written before cfg_done.
However, for most other register writes, you probably do want to always use
writel().
> + ctx->regsbak[offset >> 2] = v;
> +}
> +
> +static inline uint32_t vop_readl(struct vop_context *ctx, uint32_t offset)
> +{
> + return readl(ctx->regs + offset);
> +}
> +
> +static inline void vop_cfg_done(struct vop_context *ctx)
> +{
> + writel(0x01, ctx->regs + REG_CFG_DONE);
> +}
> +
> +static inline void vop_mask_write(struct vop_context *ctx,
> + uint32_t offset, uint32_t mask, uint32_t v)
> +{
> + if (mask) {
> + uint32_t cached_val = ctx->regsbak[offset >> 2];
> +
> + cached_val = (cached_val & ~mask) | v;
> + writel(cached_val, ctx->regs + offset);
> + ctx->regsbak[offset >> 2] = cached_val;
> + }
> +}
> +
> +static inline struct vop_driver_data *vop_get_driver_data(struct device *dev)
> +{
> + const struct of_device_id *of_id =
> + of_match_device(vop_driver_dt_match, dev);
> +
> + return (struct vop_driver_data *)of_id->data;
> +}
> +
> +static enum vop_data_format vop_convert_format(uint32_t format)
> +{
> + switch (format) {
> + case DRM_FORMAT_XRGB8888:
> + case DRM_FORMAT_ARGB8888:
> + return VOP_FMT_ARGB8888;
> + case DRM_FORMAT_RGB888:
> + return VOP_FMT_RGB888;
> + case DRM_FORMAT_RGB565:
> + return VOP_FMT_RGB565;
> + case DRM_FORMAT_NV12:
> + return VOP_FMT_YUV420SP;
> + case DRM_FORMAT_NV16:
> + return VOP_FMT_YUV422SP;
> + case DRM_FORMAT_NV24:
> + return VOP_FMT_YUV444SP;
> + default:
> + DRM_ERROR("unsupport format[%08x]\n", format);
> + return -EINVAL;
> + }
> +}
> +
> +static bool is_alpha_support(uint32_t format)
> +{
> + switch (format) {
> + case DRM_FORMAT_ARGB8888:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +/* TODO(djkurtz): move generic 'setup slave rk_iommu' code somewhere common */
> +static int vop_iommu_init(struct vop_context *ctx)
> +{
> + struct device *dev = ctx->dev;
> + struct device_node *np = dev->of_node;
> + struct platform_device *pd;
> + int count;
> + int ret;
> + struct of_phandle_args args;
> +
> + /* Each VOP must have exactly one iommu node, with no args */
> + count = of_count_phandle_with_args(np, "iommus", "#iommu-cells");
> + if (count != 1) {
> + dev_err(dev, "of_count_phandle_with_args(%s) => %d\n",
> + np->full_name, count);
> + return -EINVAL;
> + }
> +
> + ret = of_parse_phandle_with_args(np, "iommus", "#iommu-cells", 0,
> + &args);
> + if (ret) {
> + dev_err(dev, "of_parse_phandle_with_args(%s) => %d\n",
> + np->full_name, ret);
> + return ret;
> + }
> + if (args.args_count != 0) {
> + dev_err(dev, "incorrect number of iommu params found for %s (found %d, expected 0)\n",
> + args.np->full_name, args.args_count);
> + return -EINVAL;
> + }
> +
> + pd = of_find_device_by_node(args.np);
> + of_node_put(args.np);
> + if (!pd) {
> + dev_err(dev, "iommu %s not found\n", args.np->full_name);
> + return -EPROBE_DEFER;
> + }
> +
> + /* TODO(djkurtz): handle multiple slave iommus for a single master */
> + dev->archdata.iommu = &pd->dev;
> +
> + ret = rockchip_drm_dma_attach_device(ctx->drm_dev, dev);
> + if (ret) {
> + dev_err(dev, "failed to attach to drm dma mapping, %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void vop_iommu_fini(struct vop_context *ctx)
> +{
> + rockchip_drm_dma_detach_device(ctx->drm_dev, ctx->dev);
> +}
> +
> +static int vop_clk_enable(struct vop_context *ctx)
> +{
> + int ret;
if (ctx->clk_on)
return 0;
> +
> + if (!ctx->clk_on) {
> + ret = clk_enable(ctx->hclk);
> + if (ret < 0) {
> + dev_err(ctx->dev, "failed to enable hclk\n");
I find it nice to print the errno, to:
dev_err(ctx->dev, "failed to enable hclk, %d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_enable(ctx->dclk);
> + if (ret < 0) {
> + dev_err(ctx->dev, "failed to enable dclk\n");
> + goto err_dclk;
goto err_disable_hclk;
> + }
> +
> + ret = clk_enable(ctx->aclk);
> + if (ret < 0) {
> + dev_err(ctx->dev, "failed to enable aclk\n");
> + goto err_aclk;;
goto err_disable_dclk;
> + }
> + ctx->clk_on = true;
> + }
> +
> + return ret;
Hm, didn't the compiler complain about ret possibly being returned
undefined here?
> +err_aclk:
> + clk_disable(ctx->aclk);
> +err_dclk:
> + clk_disable(ctx->dclk);
err_disable_dclk:
clk_disable(ctx->dclk);
err_disable_hclk:
clk_disable(ctx->hclk);
> + return ret;
> +}
> +
> +static void vop_clk_disable(struct vop_context *ctx)
> +{
if (ctx->clk_on)
return;
> + if (ctx->clk_on) {
> + clk_disable(ctx->aclk);
> + clk_disable(ctx->dclk);
> + clk_disable(ctx->hclk);
> + ctx->clk_on = false;
> + }
> +}
> +
> +static void vop_power_on(struct vop_context *ctx)
> +{
> + if (vop_clk_enable(ctx) < 0) {
> + dev_err(ctx->dev, "failed to enable clks\n");
> + return;
> + }
> +
> + spin_lock(&ctx->reg_lock);
> +
> + VOP_CTRL_SET(ctx, standby, 0);
> +
> + spin_unlock(&ctx->reg_lock);
> +}
> +
> +static void vop_power_off(struct vop_context *ctx)
> +{
> + spin_lock(&ctx->reg_lock);
> +
> + VOP_CTRL_SET(ctx, standby, 1);
> +
> + spin_unlock(&ctx->reg_lock);
> +
> + vop_clk_disable(ctx);
> +}
> +
> +/*
> + * @start: starting pixel in screen coordinates
> + * @length: length of buffer "row" in pixels
> + * @end: one pixel past end of buffer in screen coordinates
> + * @last: width of screen, or one pixel past end of screen
> + * @size: number of pixels of buffer to display on scree
> + */
> +static int rockchip_plane_get_size(int start, unsigned length, unsigned last)
> +{
> + int end = start + length;
> + int size = 0;
> +
> + if (start <= 0) {
> + if (end > 0)
> + size = min_t(unsigned, end, last);
> + } else if (start < last) {
> + size = min_t(unsigned, last - start, length);
> + }
> +
> + return size;
> +}
> +
> +static int rockchip_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
> + struct drm_framebuffer *fb, int crtc_x,
> + int crtc_y, unsigned int crtc_w,
> + unsigned int crtc_h, uint32_t src_x,
> + uint32_t src_y, uint32_t src_w, uint32_t src_h)
> +{
> + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
> + const struct vop_win *win = rockchip_plane->win;
> + struct vop_context *ctx = to_vop_ctx(crtc);
> + struct drm_gem_object *obj;
> + struct rockchip_gem_object *rk_obj;
> + unsigned long offset;
> + unsigned int actual_w;
> + unsigned int actual_h;
> + unsigned int dsp_stx;
> + unsigned int dsp_sty;
> + unsigned int y_vir_stride;
> + dma_addr_t yrgb_mst;
> + enum vop_data_format format;
> + uint32_t val;
> + bool is_alpha;
> +
> + src_w >>= 16;
> + src_h >>= 16;
> + src_x >>= 16;
> + src_y >>= 16;
> +
> + if (crtc_w != src_w || crtc_h != src_h)
> + return -EINVAL;
Can we use drm_plane_helper_check_update() here instead of doing our own checks?
> +
> + actual_w = rockchip_plane_get_size(crtc_x,
> + crtc_w, crtc->mode.hdisplay);
> + actual_h = rockchip_plane_get_size(crtc_y,
> + crtc_h, crtc->mode.vdisplay);
> + if (!actual_w || !actual_h)
> + return -EINVAL;
> +
> + if (crtc_x < 0) {
> + if (actual_w)
> + src_x -= crtc_x;
> + crtc_x = 0;
> + }
> +
> + if (crtc_y < 0) {
> + if (actual_h)
> + src_y -= crtc_y;
> + crtc_y = 0;
> + }
> +
> + is_alpha = is_alpha_support(fb->pixel_format);
> + format = vop_convert_format(fb->pixel_format);
> + if (format < 0)
> + return format;
> +
> + obj = rockchip_fb_get_gem_obj(fb, 0);
> + if (!obj) {
> + DRM_ERROR("fail to get rockchip gem object from framebuffer\n");
> + return -EINVAL;
> + }
> +
> + rk_obj = to_rockchip_obj(obj);
> +
> + yrgb_mst = rk_obj->dma_addr;
> +
> + dsp_stx = crtc_x + crtc->mode.htotal - crtc->mode.hsync_start;
> + dsp_sty = crtc_y + crtc->mode.vtotal - crtc->mode.vsync_start;
> +
> + offset = src_x * (fb->bits_per_pixel >> 3);
> + offset += src_y * fb->pitches[0];
> +
> + y_vir_stride = fb->pitches[0] / (fb->bits_per_pixel >> 3);
> +
> + spin_lock(&ctx->reg_lock);
> +
> + VOP_WIN_SET(ctx, win, format, format);
> + VOP_WIN_SET(ctx, win, yrgb_vir, y_vir_stride);
> + yrgb_mst += offset;
> + VOP_WIN_SET(ctx, win, yrgb_mst, yrgb_mst);
> + val = ((actual_h - 1) & 0xffff) << 16;
> + val |= (actual_w - 1) & 0xffff;
> + VOP_WIN_SET(ctx, win, act_info, val);
> + VOP_WIN_SET(ctx, win, dsp_info, val);
> + val = ((dsp_sty - 1) & 0xffff) << 16;
> + val |= (dsp_stx - 1) & 0xffff;
> + VOP_WIN_SET(ctx, win, dsp_st, val);
> +
It looks a bit funny to me, but according to the TRM I am looking at:
act mask is 0x1fff
dsp_info is 0x0fff
dsp_st is 0x1fff
But these would be better as macros:
#define ACT_INFO_VAL(x) ((x - 1) & 0x1fff)
#define DSP_INFO_VAL(x) ((x - 1) & 0x0fff)
#define DSP_ST_VAL(x) ((x - 1) & 0x1fff)
#define ACT_INFO(w, h) ((ACT_INFO_VAL(h) << 16) | (ACT_INFO_VAL(w))
#define DSP_INFO(w, h) ((DSP_INFO_VAL(h) << 16) | (DSP_INFO_VAL(w))
#define DSP_INFO(x, y) ((DSP_ST_VAL(y) << 16) | (DSP_ST_VAL(x))
> + if (is_alpha) {
> + VOP_WIN_SET(ctx, win, dst_alpha_ctl,
> + DST_FACTOR_M0(ALPHA_SRC_INVERSE));
> + val = SRC_ALPHA_EN(1) | SRC_COLOR_M0(ALPHA_SRC_PRE_MUL) |
> + SRC_ALPHA_M0(ALPHA_STRAIGHT) |
> + SRC_BLEND_M0(ALPHA_PER_PIX) |
> + SRC_ALPHA_CAL_M0(ALPHA_NO_SATURATION) |
> + SRC_FACTOR_M0(ALPHA_ONE);
> + VOP_WIN_SET(ctx, win, src_alpha_ctl, val);
> + } else {
> + VOP_WIN_SET(ctx, win, src_alpha_ctl, SRC_ALPHA_EN(0));
> + }
> +
> + VOP_WIN_SET(ctx, win, enable, 1);
> +
> + spin_unlock(&ctx->reg_lock);
> +
> + mutex_lock(&ctx->vsync_mutex);
> +
> + /*
> + * Because the buffer set to vop take effect at frame start time,
> + * we need make sure old buffer is not in use before we release
> + * it.
> + * reference the framebuffer, and unference it when it swap out of vop.
> + */
> + if (fb != rockchip_plane->front_fb) {
> + drm_framebuffer_reference(fb);
> + if (rockchip_plane->pending_fb)
> + drm_framebuffer_unreference(rockchip_plane->pending_fb);
> + rockchip_plane->pending_fb = fb;
> + rockchip_plane->pending_yrgb_mst = yrgb_mst;
> + ctx->vsync_work_pending = true;
> + }
> + rockchip_plane->enabled = true;
> +
> + mutex_unlock(&ctx->vsync_mutex);
> +
> + spin_lock(&ctx->reg_lock);
> + vop_cfg_done(ctx);
> + spin_unlock(&ctx->reg_lock);
> +
> + return 0;
> +}
> +
> +#define rockchip_update_plane_helper(plane, crtc, fb, x, y) \
> + rockchip_update_plane(plane, crtc, fb, 0, 0, \
> + (fb)->width, (fb)->height, \
> + (x) << 16, (y) << 16, \
> + (fb)->width << 16, (fb)->height << 16)
(1) No. This is using the framebuffer size. We should be using the
crtc's mode's
dimensions to display the primary plane.
(2) Also, please make this an inline function named
rockchip_update_primary_plane()
> +
> +static int rockchip_disable_plane(struct drm_plane *plane)
> +{
> + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
> + const struct vop_win *win = rockchip_plane->win;
> + struct vop_context *ctx;
> +
> + if (!plane->crtc)
> + return 0;
> +
> + ctx = to_vop_ctx(plane->crtc);
> + spin_lock(&ctx->reg_lock);
> +
> + VOP_WIN_SET(ctx, win, enable, 0);
> + vop_cfg_done(ctx);
> +
> + spin_unlock(&ctx->reg_lock);
> +
> + mutex_lock(&ctx->vsync_mutex);
> +
> + /*
> + * clear the pending framebuffer and set vsync_work_pending true,
> + * so that the framebuffer will unref at the next vblank.
> + */
> + if (rockchip_plane->pending_fb) {
> + drm_framebuffer_unreference(rockchip_plane->pending_fb);
> + rockchip_plane->pending_fb = NULL;
> + }
> +
> + rockchip_plane->enabled = false;
> + ctx->vsync_work_pending = true;
> +
> + mutex_unlock(&ctx->vsync_mutex);
> +
> + return 0;
> +}
> +
> +static void rockchip_plane_destroy(struct drm_plane *plane)
> +{
> + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
> + struct vop_context *ctx = to_vop_ctx(plane->crtc);
> +
> + rockchip_disable_plane(plane);
> + drm_plane_cleanup(plane);
> + ctx->win_mask &= ~(1 << rockchip_plane->id);
> + kfree(rockchip_plane);
> +}
> +
> +static const struct drm_plane_funcs rockchip_plane_funcs = {
> + .update_plane = rockchip_update_plane,
> + .disable_plane = rockchip_disable_plane,
> + .destroy = rockchip_plane_destroy,
> +};
> +
> +static struct drm_plane *rockchip_plane_init(struct vop_context *ctx,
> + unsigned long possible_crtcs,
> + enum drm_plane_type type,
> + int index)
> +{
> + struct rockchip_plane *rockchip_plane;
> + struct vop_driver_data *vop_data = ctx->data;
> + const struct vop_win *win;
> + int err;
> +
> + if (index >= VOP_MAX_WIN_SUPPORT)
> + return ERR_PTR(-EINVAL);
> +
> + rockchip_plane = kzalloc(sizeof(*rockchip_plane), GFP_KERNEL);
> + if (!rockchip_plane)
> + return ERR_PTR(-ENOMEM);
> +
> + win = vop_data->win[index];
> + ctx->win_mask |= (1 << index);
> + rockchip_plane->id = index;
> + rockchip_plane->win = win;
> +
> + err = drm_universal_plane_init(ctx->drm_dev, &rockchip_plane->base,
> + possible_crtcs, &rockchip_plane_funcs,
> + win->phy->data_formats,
> + win->phy->nformats, type);
> + if (err) {
> + DRM_ERROR("failed to initialize plane\n");
> + kfree(rockchip_plane);
> + return ERR_PTR(err);
> + }
> +
> + return &rockchip_plane->base;
> +}
> +
> +int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type)
> +{
> + struct vop_context *ctx = to_vop_ctx(crtc);
> +
> + if (ctx->dpms != DRM_MODE_DPMS_ON)
> + return -EPERM;
> +
> + switch (connector_type) {
> + case DRM_MODE_CONNECTOR_LVDS:
> + VOP_CTRL_SET(ctx, rgb_en, 1);
> + VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_P888);
> + break;
> + case DRM_MODE_CONNECTOR_eDP:
> + VOP_CTRL_SET(ctx, edp_en, 1);
> + VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_AAAA);
> + break;
> + case DRM_MODE_CONNECTOR_HDMIA:
> + VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_AAAA);
> + VOP_CTRL_SET(ctx, hdmi_en, 1);
> + break;
> + default:
> + DRM_ERROR("unsupport connector_type[%d]\n", connector_type);
> + return -EINVAL;
> + };
> +
> + return 0;
> +}
> +
> +int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe)
> +{
> + struct vop_context *ctx = to_vop_ctx(rockchip_drm_find_crtc(dev, pipe));
> + unsigned long flags;
> +
> + if (ctx->dpms != DRM_MODE_DPMS_ON)
> + return -EPERM;
> +
> + spin_lock_irqsave(&ctx->irq_lock, flags);
> +
> + vop_mask_write(ctx, INTR_CTRL0, LINE_FLAG_INTR_MASK,
> + LINE_FLAG_INTR_EN(1));
> +
> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
> +
> + return 0;
> +}
> +
> +void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe)
> +{
> + struct vop_context *ctx = to_vop_ctx(rockchip_drm_find_crtc(dev, pipe));
> + unsigned long flags;
> +
> + if (ctx->dpms != DRM_MODE_DPMS_ON)
> + return;
> + spin_lock_irqsave(&ctx->irq_lock, flags);
> + vop_mask_write(ctx, INTR_CTRL0, LINE_FLAG_INTR_MASK,
> + LINE_FLAG_INTR_EN(0));
> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
> +}
> +
> +static void rockchip_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
> +{
> + struct vop_context *ctx = to_vop_ctx(crtc);
> +
> + DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
> +
> + if (ctx->dpms == mode) {
> + DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
> + return;
> + }
> + if (mode > DRM_MODE_DPMS_ON) {
> + /* wait for the completion of page flip. */
> + if (!wait_event_timeout(ctx->wait_vsync_queue,
> + !atomic_read(&ctx->wait_vsync_event),
> + HZ/20))
> + DRM_DEBUG_KMS("vblank wait timed out.\n");
> + drm_vblank_off(crtc->dev, ctx->pipe);
> + }
> +
> + switch (mode) {
> + case DRM_MODE_DPMS_ON:
> + vop_power_on(ctx);
> + break;
> + case DRM_MODE_DPMS_STANDBY:
> + case DRM_MODE_DPMS_SUSPEND:
> + case DRM_MODE_DPMS_OFF:
> + vop_power_off(ctx);
> + break;
> + default:
> + DRM_DEBUG_KMS("unspecified mode %d\n", mode);
> + break;
> + }
> +
> + ctx->dpms = mode;
> +}
> +
> +static void rockchip_drm_crtc_prepare(struct drm_crtc *crtc)
> +{
> + rockchip_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +}
> +
> +static bool rockchip_drm_crtc_mode_fixup(struct drm_crtc *crtc,
> + const struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted_mode)
> +{
> + if (adjusted_mode->htotal == 0 || adjusted_mode->vtotal == 0)
> + return false;
> +
> + return true;
> +}
> +
> +static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> + struct drm_framebuffer *old_fb);
> +
> +static int rockchip_drm_crtc_mode_set(struct drm_crtc *crtc,
> + struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted_mode,
> + int x, int y,
> + struct drm_framebuffer *fb)
> +{
> + struct vop_context *ctx = to_vop_ctx(crtc);
> + u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
> + u16 hdisplay = adjusted_mode->hdisplay;
> + u16 htotal = adjusted_mode->htotal;
> + u16 hact_st = adjusted_mode->htotal - adjusted_mode->hsync_start;
> + u16 hact_end = hact_st + hdisplay;
> + u16 vdisplay = adjusted_mode->vdisplay;
> + u16 vtotal = adjusted_mode->vtotal;
> + u16 vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
> + u16 vact_st = adjusted_mode->vtotal - adjusted_mode->vsync_start;
> + u16 vact_end = vact_st + vdisplay;
> + unsigned long flags;
> + int ret;
> + uint32_t val;
> +
> + clk_disable(ctx->dclk);
> +
> + ret = rockchip_drm_crtc_mode_set_base(crtc, x, y, fb);
> + if (ret)
> + return ret;
> +
> + val = 0x8;
> + val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0;
> + val |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? (1 << 1) : 0;
> + VOP_CTRL_SET(ctx, pin_pol, val);
> +
> + VOP_CTRL_SET(ctx, htotal_pw, (htotal << 16) | hsync_len);
> + val = hact_st << 16;
> + val |= hact_end;
> + VOP_CTRL_SET(ctx, hact_st_end, val);
> + VOP_CTRL_SET(ctx, hpost_st_end, val);
> +
> + VOP_CTRL_SET(ctx, vtotal_pw, (vtotal << 16) | vsync_len);
> + val = vact_st << 16;
> + val |= vact_end;
> + VOP_CTRL_SET(ctx, vact_st_end, val);
> + VOP_CTRL_SET(ctx, vpost_st_end, val);
> +
> + spin_lock_irqsave(&ctx->irq_lock, flags);
> +
> + vop_mask_write(ctx, INTR_CTRL0, DSP_LINE_NUM_MASK,
> + DSP_LINE_NUM(vact_end));
> +
> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
> +
> + /*
> + * do dclk_reset, let all config take affect, so new clk will safely
> + * run in correct frame.
> + */
> + reset_control_assert(ctx->dclk_rst);
> + usleep_range(10, 20);
> + reset_control_deassert(ctx->dclk_rst);
> +
> + clk_set_rate(ctx->dclk, adjusted_mode->clock * 1000);
> + clk_enable(ctx->dclk);
> +
> + return 0;
> +}
> +
> +static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> + struct drm_framebuffer *old_fb)
> +{
> + int ret;
> +
> + ret = rockchip_update_plane_helper(crtc->primary, crtc,
> + crtc->primary->fb, x, y);
> + if (ret < 0) {
> + DRM_ERROR("fail to update plane\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void rockchip_drm_crtc_commit(struct drm_crtc *crtc)
> +{
> + /* just do dummy now */
> +}
> +
> +static const struct drm_crtc_helper_funcs rockchip_crtc_helper_funcs = {
> + .dpms = rockchip_drm_crtc_dpms,
> + .prepare = rockchip_drm_crtc_prepare,
> + .mode_fixup = rockchip_drm_crtc_mode_fixup,
> + .mode_set = rockchip_drm_crtc_mode_set,
> + .mode_set_base = rockchip_drm_crtc_mode_set_base,
> + .commit = rockchip_drm_crtc_commit,
> +};
> +
> +static int rockchip_drm_crtc_page_flip(struct drm_crtc *crtc,
> + struct drm_framebuffer *fb,
> + struct drm_pending_vblank_event *event,
> + uint32_t page_flip_flags)
> +{
> + struct drm_device *dev = crtc->dev;
> + struct vop_context *ctx = to_vop_ctx(crtc);
> + struct drm_framebuffer *old_fb = crtc->primary->fb;
> + int ret;
> +
> + /* when the page flip is requested, crtc's dpms should be on */
> + if (ctx->dpms > DRM_MODE_DPMS_ON) {
> + DRM_DEBUG("failed page flip request at dpms[%d].\n", ctx->dpms);
> + return 0;
> + }
> +
> + ret = drm_vblank_get(dev, ctx->pipe);
> + if (ret) {
> + DRM_DEBUG("failed to acquire vblank counter\n");
> + return ret;
> + }
> +
> + spin_lock_irq(&dev->event_lock);
> + if (ctx->event) {
> + spin_unlock_irq(&dev->event_lock);
> + DRM_ERROR("already pending flip!\n");
> + return -EBUSY;
> + }
> + ctx->event = event;
> + atomic_set(&ctx->wait_vsync_event, 1);
> + spin_unlock_irq(&dev->event_lock);
> +
> + crtc->primary->fb = fb;
> +
> + ret = rockchip_update_plane_helper(crtc->primary, crtc, fb,
> + crtc->x, crtc->y);
> + if (ret) {
> + crtc->primary->fb = old_fb;
> +
> + spin_lock_irq(&dev->event_lock);
> + drm_vblank_put(dev, ctx->pipe);
> + atomic_set(&ctx->wait_vsync_event, 0);
> + ctx->event = NULL;
> + spin_unlock_irq(&dev->event_lock);
> + }
> +
> + return ret;
> +}
> +
> +static void rockchip_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe)
> +{
> + struct rockchip_drm_private *dev_priv = dev->dev_private;
> + struct drm_crtc *drm_crtc = dev_priv->crtc[pipe];
> + struct vop_context *ctx;
> + unsigned long flags;
> +
> + ctx = to_vop_ctx(drm_crtc);
> +
> + spin_lock_irqsave(&dev->event_lock, flags);
> +
> + if (ctx->event) {
> + drm_send_vblank_event(dev, -1, ctx->event);
> + drm_vblank_put(dev, pipe);
> + atomic_set(&ctx->wait_vsync_event, 0);
> + wake_up(&ctx->wait_vsync_queue);
> + ctx->event = NULL;
> + }
> +
> + spin_unlock_irqrestore(&dev->event_lock, flags);
> +}
> +
> +static void rockchip_drm_crtc_destroy(struct drm_crtc *crtc)
> +{
> + struct vop_context *ctx = to_vop_ctx(crtc);
> + struct rockchip_drm_private *private = crtc->dev->dev_private;
> +
> + private->crtc[ctx->pipe] = NULL;
> + drm_crtc_cleanup(crtc);
> +}
> +
> +static const struct drm_crtc_funcs rockchip_crtc_funcs = {
> + .set_config = drm_crtc_helper_set_config,
> + .page_flip = rockchip_drm_crtc_page_flip,
> + .destroy = rockchip_drm_crtc_destroy,
> +};
> +
> +static void rockchip_vsync_worker(struct work_struct *work)
> +{
> + struct vop_context *ctx = container_of(work, struct vop_context,
> + vsync_work);
> + struct drm_device *drm = ctx->drm_dev;
> + struct rockchip_drm_private *dev_priv = drm->dev_private;
> + struct drm_crtc *crtc = dev_priv->crtc[ctx->pipe];
> + struct rockchip_plane *rockchip_plane;
> + struct drm_plane *plane;
> + uint32_t yrgb_mst;
> +
> + mutex_lock(&ctx->vsync_mutex);
> +
> + ctx->vsync_work_pending = false;
> +
> + list_for_each_entry(plane, &drm->mode_config.plane_list, head) {
> + rockchip_plane = to_rockchip_plane(plane);
> +
> + if (to_vop_ctx(plane->crtc) != ctx)
> + continue;
> + if (rockchip_plane->enabled && !rockchip_plane->pending_fb)
> + continue;
> + if (!rockchip_plane->enabled && !rockchip_plane->front_fb)
> + continue;
> + /*
> + * make sure the yrgb_mst take effect, so that
> + * we can unreference the old framebuffer.
> + */
> + yrgb_mst = VOP_WIN_GET_YRGBADDR(ctx, rockchip_plane->win);
> + if (rockchip_plane->pending_yrgb_mst != yrgb_mst) {
> + /*
> + * some plane no complete, unref at next vblank
> + */
> + ctx->vsync_work_pending = true;
> + continue;
> + }
> +
> + /*
> + * drm_framebuffer_unreference maybe call iommu unmap,
> + * and iommu not allow unmap buffer at irq context,
> + * so we do drm_framebuffer_unreference at queue_work.
> + */
> + if (rockchip_plane->front_fb)
> + drm_framebuffer_unreference(rockchip_plane->front_fb);
> +
> + rockchip_plane->front_fb = rockchip_plane->pending_fb;
> + rockchip_plane->pending_fb = NULL;
> +
> + /*
> + * if primary plane flip complete, sending the event to
> + * userspace
> + */
> + if (&rockchip_plane->base == crtc->primary)
> + rockchip_drm_crtc_finish_pageflip(ctx->drm_dev,
> + ctx->pipe);
> + }
> +
> + mutex_unlock(&ctx->vsync_mutex);
> +}
> +
> +static irqreturn_t rockchip_vop_isr(int irq, void *data)
> +{
> + struct vop_context *ctx = data;
> + uint32_t intr0_reg;
> + unsigned long flags;
> +
> + intr0_reg = vop_readl(ctx, INTR_CTRL0);
> + if (intr0_reg & LINE_FLAG_INTR) {
> + spin_lock_irqsave(&ctx->irq_lock, flags);
> + vop_writel(ctx, INTR_CTRL0, intr0_reg | LINE_FLAG_INTR_CLR);
> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
> + } else {
> + return IRQ_NONE;
> + }
> +
> + drm_handle_vblank(ctx->drm_dev, ctx->pipe);
> + if (ctx->vsync_work_pending)
> + queue_work(ctx->vsync_wq, &ctx->vsync_work);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int vop_create_crtc(struct vop_context *ctx)
> +{
> + struct device *dev = ctx->dev;
> + struct drm_device *drm_dev = ctx->drm_dev;
> + struct drm_plane *primary, *cursor;
> + unsigned long possible_crtcs;
> + struct drm_crtc *crtc;
> + int ret;
> + int nr;
> +
> + ctx->win_mask = 0;
> + crtc = &ctx->crtc;
> +
> + ret = rockchip_drm_add_crtc(drm_dev, crtc, dev->of_node);
> + if (ret < 0)
> + return ret;
> + ctx->pipe = ret;
> +
> + possible_crtcs = (1 << ctx->pipe);
> +
> + for (nr = 0; nr < VOP_MAX_WIN_SUPPORT; nr++) {
> + if (nr == VOP_DEFAULT_PRIMARY) {
> + primary = rockchip_plane_init(ctx, possible_crtcs,
> + DRM_PLANE_TYPE_PRIMARY,
> + VOP_DEFAULT_PRIMARY);
> + if (IS_ERR(primary)) {
> + DRM_ERROR("fail to init primary plane\n");
> + return PTR_ERR(primary);
> + }
> + } else if (nr == VOP_DEFAULT_CURSOR) {
> + cursor = rockchip_plane_init(ctx, possible_crtcs,
> + DRM_PLANE_TYPE_CURSOR,
> + VOP_DEFAULT_CURSOR);
> + if (IS_ERR(cursor)) {
> + DRM_ERROR("fail to init cursor plane\n");
> + return PTR_ERR(cursor);
> + }
> + } else {
> + struct drm_plane *plane;
> +
> + plane = rockchip_plane_init(ctx, possible_crtcs,
> + DRM_PLANE_TYPE_OVERLAY,
> + nr);
> + if (IS_ERR(plane)) {
> + DRM_ERROR("fail to init overlay plane\n");
> + return PTR_ERR(plane);
> + }
> + }
> + }
> +
> + drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
> + &rockchip_crtc_funcs);
> + drm_crtc_helper_add(crtc, &rockchip_crtc_helper_funcs);
> +
> + return 0;
> +}
> +
> +static int rockchip_vop_initial(struct vop_context *ctx)
> +{
> + struct vop_driver_data *vop_data = ctx->data;
> + const struct vop_reg_data *init_table = vop_data->init_table;
> + struct reset_control *ahb_rst;
> + int i, ret;
> +
> + ctx->hclk = devm_clk_get(ctx->dev, "hclk_vop");
> + if (IS_ERR(ctx->hclk)) {
> + dev_err(ctx->dev, "failed to get hclk source\n");
> + return PTR_ERR(ctx->hclk);
> + }
> + ctx->aclk = devm_clk_get(ctx->dev, "aclk_vop");
> + if (IS_ERR(ctx->aclk)) {
> + dev_err(ctx->dev, "failed to get aclk source\n");
> + return PTR_ERR(ctx->aclk);
> + }
> + ctx->dclk = devm_clk_get(ctx->dev, "dclk_vop");
> + if (IS_ERR(ctx->dclk)) {
> + dev_err(ctx->dev, "failed to get dclk source\n");
> + return PTR_ERR(ctx->dclk);
> + }
> +
> + ret = clk_prepare(ctx->hclk);
> + if (ret < 0) {
> + dev_err(ctx->dev, "failed to prepare hclk\n");
> + return ret;
> + }
> +
> + ret = clk_prepare(ctx->dclk);
> + if (ret < 0) {
> + dev_err(ctx->dev, "failed to prepare dclk\n");
> + goto err_prepare_hclk;
goto err_unprepare_hclk;
> + }
> +
> + ret = clk_prepare(ctx->aclk);
> + if (ret < 0) {
> + dev_err(ctx->dev, "failed to prepare aclk\n");
> + goto err_prepare_dclk;
goto err_unprepare_dclk;
> + }
> +
> + ret = vop_clk_enable(ctx);
> + if (ret < 0)
> + goto err_prepare_aclk;
goto err_unprepare_aclk;
> +
> + /*
> + * do hclk_reset, reset all vop registers.
> + */
> + ahb_rst = devm_reset_control_get(ctx->dev, "ahb");
> + if (IS_ERR(ahb_rst)) {
> + dev_err(ctx->dev, "failed to get ahb reset\n");
> + ret = PTR_ERR(ahb_rst);
> + goto err_diable_clk;
> + }
> + reset_control_assert(ahb_rst);
> + usleep_range(10, 20);
> + reset_control_deassert(ahb_rst);
> +
> + memcpy(ctx->regsbak, ctx->regs, ctx->len);
> +
> + for (i = 0; i < vop_data->table_size; i++)
> + vop_writel(ctx, init_table[i].offset, init_table[i].value);
> +
> + for (i = 0; i < VOP_MAX_WIN_SUPPORT; i++)
> + VOP_WIN_SET(ctx, vop_data->win[i], enable, 0);
> +
> + vop_cfg_done(ctx);
> +
> + /*
> + * do dclk_reset, let all win config take affect, and then we can enable
> + * iommu safe.
> + */
> + ctx->dclk_rst = devm_reset_control_get(ctx->dev, "dclk");
> + if (IS_ERR(ctx->dclk_rst)) {
> + dev_err(ctx->dev, "failed to get dclk reset\n");
> + ret = PTR_ERR(ctx->dclk_rst);
> + goto err_diable_clk;
goto err_disable_clk;
> + }
> + reset_control_assert(ctx->dclk_rst);
> + usleep_range(10, 20);
> + reset_control_deassert(ctx->dclk_rst);
> +
> + ctx->dpms = DRM_MODE_DPMS_ON;
The reset at probe time is probably a good idea.
However, this leaves the VOP enabled and clocking at... but what will it be
displaying, and what mode will it use?
Perhaps we should delay enabling clocks / dpms until the first mode set /
dpms(DPMS_ON) request?
> +
> + return 0;
> +
> +err_prepare_aclk:
> + clk_unprepare(ctx->aclk);
> +err_prepare_dclk:
> + clk_unprepare(ctx->dclk);
> +err_prepare_hclk:
> + clk_unprepare(ctx->hclk);
> +err_diable_clk:
> + vop_clk_disable(ctx);
> + return ret;
err_disable_clk:
vop_clk_disable(ctx);
err_unprepare_aclk:
clk_unprepare(ctx->aclk);
err_unprepare_dclk:
clk_unprepare(ctx->dclk);
err_unprepare_hclk:
clk_unprepare(ctx->hclk);
return ret;
That's it for now!
-djk
> +}
> +
> +static int vop_bind(struct device *dev, struct device *master, void *data)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct vop_driver_data *vop_data = vop_get_driver_data(dev);
> + struct drm_device *drm_dev = data;
> + struct vop_context *ctx;
> + struct resource *res;
> + int ret;
> +
> + if (!vop_data)
> + return -ENODEV;
> +
> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + ctx->dev = dev;
> + ctx->data = vop_data;
> + ctx->drm_dev = drm_dev;
> + dev_set_drvdata(dev, ctx);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + ctx->len = resource_size(res);
> + ctx->regs = devm_ioremap_resource(dev, res);
> + if (IS_ERR(ctx->regs))
> + return PTR_ERR(ctx->regs);
> +
> + ctx->regsbak = devm_kzalloc(dev, ctx->len, GFP_KERNEL);
> + if (!ctx->regsbak)
> + return -ENOMEM;
> +
> + ret = rockchip_vop_initial(ctx);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot initial vop dev - err %d\n", ret);
> + return ret;
> + }
> +
> + ctx->irq = platform_get_irq(pdev, 0);
> + if (ctx->irq < 0) {
> + dev_err(dev, "cannot find irq for vop\n");
> + return ctx->irq;
> + }
> +
> + spin_lock_init(&ctx->reg_lock);
> + spin_lock_init(&ctx->irq_lock);
> +
> + init_waitqueue_head(&ctx->wait_vsync_queue);
> + atomic_set(&ctx->wait_vsync_event, 0);
> +
> + ret = vop_iommu_init(ctx);
> + if (ret) {
> + DRM_ERROR("Failed to setup iommu, %d\n", ret);
> + return ret;
> + }
> +
> + ctx->vsync_wq = create_singlethread_workqueue("vsync");
> + if (!ctx->vsync_wq) {
> + dev_err(dev, "failed to create workqueue\n");
> + return -EINVAL;
> + }
> + INIT_WORK(&ctx->vsync_work, rockchip_vsync_worker);
> +
> + mutex_init(&ctx->vsync_mutex);
> + pm_runtime_enable(&pdev->dev);
> +
> + ret = devm_request_irq(dev, ctx->irq, rockchip_vop_isr,
> + IRQF_SHARED, dev_name(dev), ctx);
> + if (ret) {
> + dev_err(dev, "cannot requeset irq%d - err %d\n", ctx->irq, ret);
> + return ret;
> + }
> +
> + return vop_create_crtc(ctx);
> +}
> +
> +static void vop_unbind(struct device *dev, struct device *master,
> + void *data)
> +{
> + struct drm_device *drm_dev = data;
> + struct vop_context *ctx = dev_get_drvdata(dev);
> + struct drm_crtc *crtc = &ctx->crtc;
> +
> + drm_crtc_cleanup(crtc);
> + pm_runtime_disable(dev);
> + rockchip_drm_remove_crtc(drm_dev, ctx->pipe);
> +
> + vop_iommu_fini(ctx);
> +}
> +
> +static const struct component_ops vop_component_ops = {
> + .bind = vop_bind,
> + .unbind = vop_unbind,
> +};
> +
> +static int vop_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct vop_context *ctx;
> +
> + if (!dev->of_node) {
> + dev_err(dev, "can't find vop devices\n");
> + return -ENODEV;
> + }
> +
> + platform_set_drvdata(pdev, ctx);
> +
> + return component_add(dev, &vop_component_ops);
> +}
> +
> +static int vop_remove(struct platform_device *pdev)
> +{
> + component_del(&pdev->dev, &vop_component_ops);
> +
> + return 0;
> +}
> +
> +struct platform_driver rockchip_vop_platform_driver = {
> + .probe = vop_probe,
> + .remove = vop_remove,
> + .driver = {
> + .name = "rockchip-vop",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(vop_driver_dt_match),
> + },
> +};
> +
> +module_platform_driver(rockchip_vop_platform_driver);
> +
> +MODULE_AUTHOR("Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>");
> +MODULE_DESCRIPTION("ROCKCHIP VOP Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> new file mode 100644
> index 0000000..d2a04f0
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> @@ -0,0 +1,196 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author:Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#ifndef _ROCKCHIP_DRM_VOP_H
> +#define _ROCKCHIP_DRM_VOP_H
> +
> +/* register definition */
> +#define REG_CFG_DONE 0x0000
> +#define VERSION_INFO 0x0004
> +#define SYS_CTRL 0x0008
> +#define SYS_CTRL1 0x000c
> +#define DSP_CTRL0 0x0010
> +#define DSP_CTRL1 0x0014
> +#define DSP_BG 0x0018
> +#define MCU_CTRL 0x001c
> +#define INTR_CTRL0 0x0020
> +#define INTR_CTRL1 0x0024
> +#define WIN0_CTRL0 0x0030
> +#define WIN0_CTRL1 0x0034
> +#define WIN0_COLOR_KEY 0x0038
> +#define WIN0_VIR 0x003c
> +#define WIN0_YRGB_MST 0x0040
> +#define WIN0_CBR_MST 0x0044
> +#define WIN0_ACT_INFO 0x0048
> +#define WIN0_DSP_INFO 0x004c
> +#define WIN0_DSP_ST 0x0050
> +#define WIN0_SCL_FACTOR_YRGB 0x0054
> +#define WIN0_SCL_FACTOR_CBR 0x0058
> +#define WIN0_SCL_OFFSET 0x005c
> +#define WIN0_SRC_ALPHA_CTRL 0x0060
> +#define WIN0_DST_ALPHA_CTRL 0x0064
> +#define WIN0_FADING_CTRL 0x0068
> +/* win1 register */
> +#define WIN1_CTRL0 0x0070
> +#define WIN1_CTRL1 0x0074
> +#define WIN1_COLOR_KEY 0x0078
> +#define WIN1_VIR 0x007c
> +#define WIN1_YRGB_MST 0x0080
> +#define WIN1_CBR_MST 0x0084
> +#define WIN1_ACT_INFO 0x0088
> +#define WIN1_DSP_INFO 0x008c
> +#define WIN1_DSP_ST 0x0090
> +#define WIN1_SCL_FACTOR_YRGB 0x0094
> +#define WIN1_SCL_FACTOR_CBR 0x0098
> +#define WIN1_SCL_OFFSET 0x009c
> +#define WIN1_SRC_ALPHA_CTRL 0x00a0
> +#define WIN1_DST_ALPHA_CTRL 0x00a4
> +#define WIN1_FADING_CTRL 0x00a8
> +/* win2 register */
> +#define WIN2_CTRL0 0x00b0
> +#define WIN2_CTRL1 0x00b4
> +#define WIN2_VIR0_1 0x00b8
> +#define WIN2_VIR2_3 0x00bc
> +#define WIN2_MST0 0x00c0
> +#define WIN2_DSP_INFO0 0x00c4
> +#define WIN2_DSP_ST0 0x00c8
> +#define WIN2_COLOR_KEY 0x00cc
> +#define WIN2_MST1 0x00d0
> +#define WIN2_DSP_INFO1 0x00d4
> +#define WIN2_DSP_ST1 0x00d8
> +#define WIN2_SRC_ALPHA_CTRL 0x00dc
> +#define WIN2_MST2 0x00e0
> +#define WIN2_DSP_INFO2 0x00e4
> +#define WIN2_DSP_ST2 0x00e8
> +#define WIN2_DST_ALPHA_CTRL 0x00ec
> +#define WIN2_MST3 0x00f0
> +#define WIN2_DSP_INFO3 0x00f4
> +#define WIN2_DSP_ST3 0x00f8
> +#define WIN2_FADING_CTRL 0x00fc
> +/* win3 register */
> +#define WIN3_CTRL0 0x0100
> +#define WIN3_CTRL1 0x0104
> +#define WIN3_VIR0_1 0x0108
> +#define WIN3_VIR2_3 0x010c
> +#define WIN3_MST0 0x0110
> +#define WIN3_DSP_INFO0 0x0114
> +#define WIN3_DSP_ST0 0x0118
> +#define WIN3_COLOR_KEY 0x011c
> +#define WIN3_MST1 0x0120
> +#define WIN3_DSP_INFO1 0x0124
> +#define WIN3_DSP_ST1 0x0128
> +#define WIN3_SRC_ALPHA_CTRL 0x012c
> +#define WIN3_MST2 0x0130
> +#define WIN3_DSP_INFO2 0x0134
> +#define WIN3_DSP_ST2 0x0138
> +#define WIN3_DST_ALPHA_CTRL 0x013c
> +#define WIN3_MST3 0x0140
> +#define WIN3_DSP_INFO3 0x0144
> +#define WIN3_DSP_ST3 0x0148
> +#define WIN3_FADING_CTRL 0x014c
> +/* hwc register */
> +#define HWC_CTRL0 0x0150
> +#define HWC_CTRL1 0x0154
> +#define HWC_MST 0x0158
> +#define HWC_DSP_ST 0x015c
> +#define HWC_SRC_ALPHA_CTRL 0x0160
> +#define HWC_DST_ALPHA_CTRL 0x0164
> +#define HWC_FADING_CTRL 0x0168
> +/* post process register */
> +#define POST_DSP_HACT_INFO 0x0170
> +#define POST_DSP_VACT_INFO 0x0174
> +#define POST_SCL_FACTOR_YRGB 0x0178
> +#define POST_SCL_CTRL 0x0180
> +#define POST_DSP_VACT_INFO_F1 0x0184
> +#define DSP_HTOTAL_HS_END 0x0188
> +#define DSP_HACT_ST_END 0x018c
> +#define DSP_VTOTAL_VS_END 0x0190
> +#define DSP_VACT_ST_END 0x0194
> +#define DSP_VS_ST_END_F1 0x0198
> +#define DSP_VACT_ST_END_F1 0x019c
> +/* register definition end */
> +
> +/* interrupt define */
> +#define DSP_HOLD_VALID_INTR (1 << 0)
> +#define FS_INTR (1 << 1)
> +#define LINE_FLAG_INTR (1 << 2)
> +#define BUS_ERROR_INTR (1 << 3)
> +
> +#define DSP_HOLD_VALID_INTR_EN(x) ((x) << 4)
> +#define FS_INTR_EN(x) ((x) << 5)
> +#define LINE_FLAG_INTR_EN(x) ((x) << 6)
> +#define BUS_ERROR_INTR_EN(x) ((x) << 7)
> +#define DSP_HOLD_VALID_INTR_MASK (1 << 4)
> +#define FS_INTR_EN_MASK (1 << 5)
> +#define LINE_FLAG_INTR_MASK (1 << 6)
> +#define BUS_ERROR_INTR_MASK (1 << 7)
> +
> +#define DSP_HOLD_VALID_INTR_CLR (1 << 8)
> +#define FS_INTR_EN_CLR (1 << 9)
> +#define LINE_FLAG_INTR_CLR (1 << 10)
> +#define BUS_ERROR_INTR_CLR (1 << 11)
> +#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
> +#define DSP_LINE_NUM_MASK (0x1fff << 12)
> +
> +/* src alpha ctrl define */
> +#define SRC_FADING_VALUE(x) (((x) & 0xff) << 24)
> +#define SRC_GLOBAL_ALPHA(x) (((x) & 0xff) << 16)
> +#define SRC_FACTOR_M0(x) (((x) & 0x7) << 6)
> +#define SRC_ALPHA_CAL_M0(x) (((x) & 0x1) << 5)
> +#define SRC_BLEND_M0(x) (((x) & 0x3) << 3)
> +#define SRC_ALPHA_M0(x) (((x) & 0x1) << 2)
> +#define SRC_COLOR_M0(x) (((x) & 0x1) << 1)
> +#define SRC_ALPHA_EN(x) (((x) & 0x1) << 0)
> +/* dst alpha ctrl define */
> +#define DST_FACTOR_M0(x) (((x) & 0x7) << 6)
> +
> +/*
> + * display output interface supported by rockchip lcdc
> + */
> +#define ROCKCHIP_OUT_MODE_P888 0
> +#define ROCKCHIP_OUT_MODE_P666 1
> +#define ROCKCHIP_OUT_MODE_P565 2
> +/* for use special outface */
> +#define ROCKCHIP_OUT_MODE_AAAA 15
> +
> +enum alpha_mode {
> + ALPHA_STRAIGHT,
> + ALPHA_INVERSE,
> +};
> +
> +enum global_blend_mode {
> + ALPHA_GLOBAL,
> + ALPHA_PER_PIX,
> + ALPHA_PER_PIX_GLOBAL,
> +};
> +
> +enum alpha_cal_mode {
> + ALPHA_SATURATION,
> + ALPHA_NO_SATURATION,
> +};
> +
> +enum color_mode {
> + ALPHA_SRC_PRE_MUL,
> + ALPHA_SRC_NO_PRE_MUL,
> +};
> +
> +enum factor_mode {
> + ALPHA_ZERO,
> + ALPHA_ONE,
> + ALPHA_SRC,
> + ALPHA_SRC_INVERSE,
> + ALPHA_SRC_GLOBAL,
> +};
> +
> +#endif /* _ROCKCHIP_DRM_VOP_H */
> --
> 1.7.9.5
>
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [RESENT PATCH v7 1/3] drm: rockchip: Add basic drm driver
2014-09-29 4:01 ` Daniel Kurtz
@ 2014-09-30 0:35 ` Mark yao
0 siblings, 0 replies; 6+ messages in thread
From: Mark yao @ 2014-09-30 0:35 UTC (permalink / raw)
To: Daniel Kurtz
Cc: Mark Rutland, Heiko Stübner, linux-doc, Kever Yang,
dri-devel, linux-kernel@vger.kernel.org, Jianqun Xu, Chris Zhong,
linux-api, jeff chen, linux-rockchip, kfx, Grant Likely,
王晓腾, Tao Huang, devicetree, Pawel Moll,
Ian Campbell, yxj, Eddie Cai, Rob Herring,
Stéphane Marchesin, simon xue, xw
[-- Attachment #1.1: Type: text/plain, Size: 126970 bytes --]
On 2014年09月29日 12:01, Daniel Kurtz wrote:
> Hi Mark,
>
> More review comments inline... (sorry for the delay)
Thanks for your review.
>
> On Fri, Sep 26, 2014 at 6:55 PM, Mark Yao <mark.yao@rock-chips.com> wrote:
>> From: Mark yao <mark.yao@rock-chips.com>
>>
>> This patch adds the basic structure of a DRM Driver for Rockchip Socs.
>>
>> Signed-off-by: Mark Yao <mark.yao@rock-chips.com>
>> Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
>> Acked-by: Daniel Vetter <daniel@ffwll.ch>
>> Reviewed-by: Rob Clark <robdclark@gmail.com>
>> ---
>> Changes in v2:
>> - use the component framework to defer main drm driver probe
>> until all VOP devices have been probed.
>> - use dma-mapping API with ARM_DMA_USE_IOMMU, create dma mapping by
>> master device and each vop device can shared the drm dma mapping.
>> - use drm_crtc_init_with_planes and drm_universal_plane_init.
>> - remove unnecessary middle layers.
>> - add cursor set, move funcs to rockchip drm crtc.
>> - use vop reset at first init
>> - reference framebuffer when used and unreference when swap out vop
>>
>> Changes in v3:
>> - change "crtc->fb" to "crtc->primary-fb"
>> Adviced by Daniel Vetter
>> - init cursor plane with universal api, remove unnecessary cursor set,move
>>
>> Changes in v4:
>> Adviced by David Herrmann
>> - remove drm_platform_*() usage, use register drm device directly.
>> Adviced by Rob Clark
>> - remove special mmap ioctl, do userspace mmap with normal mmap() or mmap offset
>>
>> Changes in v5:
>> Adviced by Arnd Bergmann
>> - doing DMA start with a 32-bit masks with dma_mask and dma_coherent_mark
>> - fix some incorrect dependencies.
>> Adviced by Boris BREZILLON
>> - fix some mistake and bugs.
>> Adviced by Daniel Vetter
>> - drop all special ioctl and use generic kms ioctl instead.
>> Adviced by Rob Clark
>> - use unlocked api for drm_fb_helper_restore_fbdev_mode.
>> - remove unused rockchip_gem_prime_import_sg_table.
>>
>> Changes in v6:
>> - set gem buffer pitch 64 bytes align, needed by mali gpu.
>> Adviced by Daniel Kurtz
>> - fix some mistake, bugs, remove unused define, more better code style etc.
>> - use clk_prepare()/unprepare() at probe()/remove() and clk_enable()/disable()
>> at runtime instead of clk_prepare_enable().
>> - provide a help function from vop for encoder to do mode config, instead of
>> using drm_diaplay_mode private method.
>> - change vop mode_set timing to make it more safely.
>>
>> Changes in v7:
>> - fix memory leakage problem
>>
>> drivers/gpu/drm/Kconfig | 2 +
>> drivers/gpu/drm/Makefile | 1 +
>> drivers/gpu/drm/rockchip/Kconfig | 17 +
>> drivers/gpu/drm/rockchip/Makefile | 8 +
>> drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 516 +++++++++
>> drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 61 ++
>> drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 ++++
>> drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
>> drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 230 ++++
>> drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 20 +
>> drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 345 ++++++
>> drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 55 +
>> drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1422 +++++++++++++++++++++++++
>> drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 196 ++++
>> 14 files changed, 3102 insertions(+)
>> create mode 100644 drivers/gpu/drm/rockchip/Kconfig
>> create mode 100644 drivers/gpu/drm/rockchip/Makefile
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.h
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.c
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.h
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.c
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.h
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.c
>> create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>>
>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>> index b066bb3..7c4c3c6 100644
>> --- a/drivers/gpu/drm/Kconfig
>> +++ b/drivers/gpu/drm/Kconfig
>> @@ -171,6 +171,8 @@ config DRM_SAVAGE
>>
>> source "drivers/gpu/drm/exynos/Kconfig"
>>
>> +source "drivers/gpu/drm/rockchip/Kconfig"
>> +
>> source "drivers/gpu/drm/vmwgfx/Kconfig"
>>
>> source "drivers/gpu/drm/gma500/Kconfig"
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index 4a55d59..d03387a 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -52,6 +52,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/
>> obj-$(CONFIG_DRM_VIA) +=via/
>> obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
>> obj-$(CONFIG_DRM_EXYNOS) +=exynos/
>> +obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
>> obj-$(CONFIG_DRM_GMA500) += gma500/
>> obj-$(CONFIG_DRM_UDL) += udl/
>> obj-$(CONFIG_DRM_AST) += ast/
>> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
>> new file mode 100644
>> index 0000000..87255f7
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/Kconfig
>> @@ -0,0 +1,17 @@
>> +config DRM_ROCKCHIP
>> + tristate "DRM Support for Rockchip"
>> + depends on DRM && ROCKCHIP_IOMMU && ARM_DMA_USE_IOMMU && IOMMU_API
>> + select DRM_KMS_HELPER
>> + select DRM_KMS_FB_HELPER
>> + select DRM_PANEL
>> + select FB_CFB_FILLRECT
>> + select FB_CFB_COPYAREA
>> + select FB_CFB_IMAGEBLIT
>> + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
>> + select VIDEOMODE_HELPERS
>> + help
>> + Choose this option if you have a Rockchip soc chipset.
>> + This driver provides kernel mode setting and buffer
>> + management to userspace. This driver does not provides
>> + 2D or 3D acceleration; acceleration is performed by other
>> + IP found on the SoC.
>> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
>> new file mode 100644
>> index 0000000..b3a5193
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/Makefile
>> @@ -0,0 +1,8 @@
>> +#
>> +# Makefile for the drm device driver. This driver provides support for the
>> +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
>> +
>> +rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o rockchip_drm_fbdev.o \
>> + rockchip_drm_gem.o rockchip_drm_vop.o
>> +
>> +obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
>> new file mode 100644
>> index 0000000..a571acc
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
>> @@ -0,0 +1,516 @@
>> +/*
>> + * 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 <asm/dma-iommu.h>
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_fb_helper.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/component.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
>> +
>> +/*
>> + * 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 dma_iommu_mapping *mapping = drm_dev->dev->archdata.mapping;
>> + int ret;
>> +
>> + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
>> + if (ret)
>> + return ret;
>> +
>> + dma_set_max_seg_size(dev, 0xffffffffu);
> Perhaps:
> dma_set_max_seg_size(dev, DMA_BIT_MASK(32));
>
OK.
>> +
>> + return arm_iommu_attach_device(dev, mapping);
>> +}
>> +
>> +void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
>> + struct device *dev)
>> +{
>> + arm_iommu_detach_device(drm_dev->dev);
>> +}
>> +
>> +static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags)
>> +{
>> + struct rockchip_drm_private *private;
>> + struct dma_iommu_mapping *mapping;
>> + struct device *dev = drm_dev->dev;
>> + int ret;
>> +
>> + private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
>> + if (!private)
>> + return -ENOMEM;
>> +
>> + drm_dev->dev_private = private;
>> +
>> + drm_mode_config_init(drm_dev);
>> +
>> + rockchip_drm_mode_config_init(drm_dev);
>> +
>> + dev->dma_parms = devm_kzalloc(dev, sizeof(*dev->dma_parms),
>> + GFP_KERNEL);
>> + if (!dev->dma_parms) {
>> + ret = -ENOMEM;
>> + goto err_config_cleanup;
>> + }
>> +
>> + /* TODO(djkurtz): fetch the mapping start/size from somewhere */
>> + mapping = arm_iommu_create_mapping(&platform_bus_type, 0x10000000,
>> + SZ_1G);
>
> Can we make this bigger, and aligned to the requested size?
> I find it makes it easier to debug that way.
>
> How about:
> mapping = arm_iommu_create_mapping(&platform_bus_type, 0x80000000, SZ_2G);
>
> In fact, is there any reason why we do not just make this a 4GB mapping starting
> at 0x00000000? Is that even possible?
OK, We can make mapping start at 0x00000000
>
>
>> + if (IS_ERR(mapping)) {
>> + ret = PTR_ERR(mapping);
>> + goto err_config_cleanup;
>> + }
>> +
>> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
>> + if (ret)
>> + goto err_release_mapping;
>> +
>> + dma_set_max_seg_size(dev, 0xffffffffu);
> see above.
>
OK
>> +
>> + ret = arm_iommu_attach_device(dev, mapping);
>> + if (ret)
>> + goto err_release_mapping;
>> +
>> + /* Try to bind all sub drivers. */
>> + ret = component_bind_all(dev, drm_dev);
>> + if (ret)
>> + goto err_detach_device;
>> +
>> + /* 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;
>> +
>> + /*
>> + * with vblank_disable_allowed = true, vblank interrupt will be disabled
>> + * by drm timer once a current process gives up ownership of
>> + * vblank event.(after drm_vblank_put function is called)
>> + */
>> + drm_dev->vblank_disable_allowed = true;
>> +
>> + ret = drm_vblank_init(drm_dev, ROCKCHIP_MAX_CRTC);
>> + if (ret)
>> + goto err_kms_helper_poll_fini;
>> +
>> + rockchip_drm_fbdev_init(drm_dev);
>> +
>> + /* force connectors detection */
>> + drm_helper_hpd_irq_event(drm_dev);
>> +
>> + return 0;
>> +
>> +err_kms_helper_poll_fini:
>> + drm_kms_helper_poll_fini(drm_dev);
>> + component_unbind_all(dev, drm_dev);
>> +err_detach_device:
>> + arm_iommu_detach_device(dev);
>> +err_release_mapping:
>> + arm_iommu_release_mapping(dev->archdata.mapping);
>> +err_config_cleanup:
>> + drm_mode_config_cleanup(drm_dev);
>> + drm_dev->dev_private = NULL;
>> + return ret;
>> +}
>> +
>> +static int rockchip_drm_unload(struct drm_device *drm_dev)
>> +{
>> + struct device *dev = drm_dev->dev;
>> +
>> + drm_kms_helper_poll_fini(drm_dev);
>> + component_unbind_all(dev, drm_dev);
>> + arm_iommu_detach_device(dev);
>> + arm_iommu_release_mapping(dev->archdata.mapping);
>> + drm_mode_config_cleanup(drm_dev);
>> + drm_dev->dev_private = NULL;
>> +
>> + return 0;
>> +}
>> +
>> +void rockchip_drm_lastclose(struct drm_device *dev)
>> +{
>> + struct rockchip_drm_private *priv = dev->dev_private;
>> +
>> + if (priv->fb_helper)
>> + drm_fb_helper_restore_fbdev_mode_unlocked(priv->fb_helper);
>> +}
>> +
>> +static const struct file_operations rockchip_drm_driver_fops = {
>> + .owner = THIS_MODULE,
>> + .open = drm_open,
>> + .mmap = rockchip_drm_gem_mmap,
>> + .poll = drm_poll,
>> + .read = drm_read,
>> + .unlocked_ioctl = drm_ioctl,
>> +#ifdef CONFIG_COMPAT
>> + .compat_ioctl = drm_compat_ioctl,
>> +#endif
>> + .release = drm_release,
>> +};
>> +
>> +const struct vm_operations_struct rockchip_drm_vm_ops = {
>> + .open = drm_gem_vm_open,
>> + .close = drm_gem_vm_close,
>> +};
>> +
>> +static struct drm_driver rockchip_drm_driver = {
>> + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
>> + .load = rockchip_drm_load,
>> + .unload = rockchip_drm_unload,
>> + .lastclose = rockchip_drm_lastclose,
>> + .get_vblank_counter = drm_vblank_count,
>> + .enable_vblank = rockchip_drm_crtc_enable_vblank,
>> + .disable_vblank = rockchip_drm_crtc_disable_vblank,
>> + .gem_vm_ops = &rockchip_drm_vm_ops,
>> + .gem_free_object = 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_prime_mmap,
>> + .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 int rockchip_drm_suspend(struct drm_device *dev, pm_message_t state)
>> +{
>> + struct drm_connector *connector;
>> +
>> + drm_modeset_lock_all(dev);
>> + list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
>> + int old_dpms = connector->dpms;
>> +
>> + if (connector->funcs->dpms)
>> + connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
>> +
>> + /* Set the old mode back to the connector for resume */
>> + connector->dpms = old_dpms;
>> + }
>> + drm_modeset_unlock_all(dev);
>> +
>> + return 0;
>> +}
>> +
>> +static int rockchip_drm_resume(struct drm_device *dev)
>> +{
>> + struct drm_connector *connector;
>> +
>> + drm_modeset_lock_all(dev);
>> + list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
>> + if (connector->funcs->dpms)
>> + connector->funcs->dpms(connector, connector->dpms);
>> + }
>> + drm_modeset_unlock_all(dev);
>> +
>> + drm_helper_resume_force_mode(dev);
>> +
>> + return 0;
>> +}
>> +
>> +static int rockchip_drm_sys_suspend(struct device *dev)
>> +{
>> + struct drm_device *drm_dev = dev_get_drvdata(dev);
>> + pm_message_t message;
>> +
>> + if (pm_runtime_suspended(dev))
>> + return 0;
>> +
>> + message.event = PM_EVENT_SUSPEND;
>> +
>> + return rockchip_drm_suspend(drm_dev, message);
>> +}
>> +
>> +static int rockchip_drm_sys_resume(struct device *dev)
>> +{
>> + struct drm_device *drm_dev = dev_get_drvdata(dev);
>> +
>> + if (!pm_runtime_suspended(dev))
>> + return 0;
>> +
>> + return rockchip_drm_resume(drm_dev);
>> +}
>> +#endif
>> +
>> +static const struct dev_pm_ops rockchip_drm_pm_ops = {
>> + SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
>> + rockchip_drm_sys_resume)
>> +};
>> +
>> +int rockchip_drm_add_crtc(struct drm_device *drm, struct drm_crtc *crtc,
>> + struct device_node *np)
>> +{
>> + struct rockchip_drm_private *priv = drm->dev_private;
>> + struct device_node *port;
>> + int pipe;
>> +
>> + if (priv->num_pipe >= ROCKCHIP_MAX_CRTC)
>> + return -EINVAL;
>> +
>> + port = of_get_child_by_name(np, "port");
>> + if (!port) {
>> + dev_err(drm->dev, "no port node found in %s\n",
>> + np->full_name);
>> + return -ENXIO;
>> + }
>> + pipe = priv->num_pipe++;
>> + crtc->port = port;
>> +
>> + priv->crtc[pipe] = crtc;
>> +
>> + return pipe;
>> +}
>> +
>> +void rockchip_drm_remove_crtc(struct drm_device *drm, int pipe)
>> +{
>> + struct rockchip_drm_private *priv = drm->dev_private;
>> +
>> + priv->num_pipe--;
>> + of_node_put(priv->crtc[pipe]->port);
>> + priv->crtc[pipe] = NULL;
>> +}
>> +
>> +struct drm_crtc *rockchip_drm_find_crtc(struct drm_device *drm, int pipe)
>> +{
>> + struct rockchip_drm_private *priv = drm->dev_private;
>> +
>> + if (pipe < ROCKCHIP_MAX_CRTC && priv->crtc[pipe])
>> + return priv->crtc[pipe];
>> +
>> + return NULL;
>> +}
>> +
>> +/*
>> + * @node: device tree node containing encoder input ports
>> + * @encoder: drm_encoder
>> + */
>> +int rockchip_drm_encoder_get_mux_id(struct device_node *node,
>> + struct drm_encoder *encoder)
>> +{
>> + struct device_node *ep = NULL;
>> + struct drm_crtc *crtc = encoder->crtc;
>> + struct of_endpoint endpoint;
>> + struct device_node *port;
>> + int ret;
>> +
>> + if (!node || !crtc)
>> + return -EINVAL;
>> +
>> + do {
>> + ep = of_graph_get_next_endpoint(node, ep);
>> + if (!ep)
>> + break;
>> +
>> + port = of_graph_get_remote_port(ep);
>> + of_node_put(port);
>> + if (port == crtc->port) {
>> + ret = of_graph_parse_endpoint(ep, &endpoint);
>> + return ret ?: endpoint.id;
>> + }
>> + } while (ep);
>> +
>> + return -EINVAL;
>> +}
>> +
>> +static int compare_of(struct device *dev, void *data)
>> +{
>> + struct device_node *np = data;
>> +
>> + return dev->of_node == np;
>> +}
>> +
>> +static void rockchip_add_endpoints(struct device *dev,
>> + struct component_match **match,
>> + struct device_node *port)
>> +{
>> + struct device_node *ep, *remote;
>> +
>> + for_each_child_of_node(port, ep) {
>> + remote = of_graph_get_remote_port_parent(ep);
>> + if (!remote || !of_device_is_available(remote)) {
>> + of_node_put(remote);
>> + continue;
>> + } else if (!of_device_is_available(remote->parent)) {
>> + dev_warn(dev, "parent device of %s is not available\n",
>> + remote->full_name);
>> + of_node_put(remote);
>> + continue;
>> + }
>> +
>> + component_match_add(dev, match, compare_of, remote);
>> + of_node_put(remote);
>> + }
>> +}
>> +
>> +static int rockchip_drm_bind(struct device *dev)
>> +{
>> + struct drm_device *drm;
>> + int ret;
>> +
>> + drm = drm_dev_alloc(&rockchip_drm_driver, dev);
>> + if (!drm)
>> + return -ENOMEM;
>> +
>> + ret = drm_dev_set_unique(drm, "%s", dev_name(dev));
>> + if (ret)
>> + goto err_free;
>> +
>> + ret = drm_dev_register(drm, 0);
>> + if (ret)
>> + goto err_free;
>> +
>> + dev_set_drvdata(dev, drm);
>> +
>> + return 0;
>> +
>> +err_free:
>> + drm_dev_unref(drm);
>> + return ret;
>> +}
>> +
>> +static void rockchip_drm_unbind(struct device *dev)
>> +{
>> + struct drm_device *drm = dev_get_drvdata(dev);
>> +
>> + drm_dev_unregister(drm);
>> + drm_dev_unref(drm);
>> +}
>> +
>> +static const struct component_master_ops rockchip_drm_ops = {
>> + .bind = rockchip_drm_bind,
>> + .unbind = rockchip_drm_unbind,
>> +};
>> +
>> +static int rockchip_drm_platform_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct component_match *match = NULL;
>> + struct device_node *np = dev->of_node;
>> + struct device_node *port;
>> + int i;
>> + int ret;
>> +
>> + if (!np)
>> + return -ENODEV;
>> + /*
>> + * Bind the crtc ports first, so that
>> + * drm_of_find_possible_crtcs called from encoder .bind callbacks
>> + * works as expected.
>> + */
>> + for (i = 0;; i++) {
>> + port = of_parse_phandle(np, "ports", i);
>> + if (!port)
>> + break;
>> +
>> + if (!of_device_is_available(port->parent)) {
>> + of_node_put(port);
>> + continue;
>> + }
>> +
>> + component_match_add(dev, &match, compare_of, port->parent);
>> + of_node_put(port);
>> + }
>> +
>> + if (i == 0) {
>> + dev_err(dev, "missing 'ports' property\n");
>> + return -ENODEV;
>> + }
>> + /*
>> + * For each bound crtc, bind the encoders attached to its
>> + * remote endpoint.
>> + */
>> + for (i = 0;; i++) {
>> + port = of_parse_phandle(np, "ports", i);
>> + if (!port)
>> + break;
>> +
>> + if (!of_device_is_available(port->parent)) {
>> + of_node_put(port);
>> + continue;
>> + }
>> +
>> + rockchip_add_endpoints(dev, &match, port);
>> + of_node_put(port);
>> + }
>> +
>> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
>> + if (ret)
>> + return ret;
> This is in drm_load() now.
OK
>> +
>> + 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 = {
>> + .owner = THIS_MODULE,
>> + .name = "rockchip-drm",
>> + .of_match_table = rockchip_drm_dt_ids,
>> + .pm = &rockchip_drm_pm_ops,
>> + },
>> +};
>> +
>> +module_platform_driver(rockchip_drm_platform_driver);
>> +
>> +MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
>> +MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
>> new file mode 100644
>> index 0000000..98094ecf
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
>> @@ -0,0 +1,61 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * based on exynos_drm_drv.h
>> + *
>> + * 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.
>> + */
>> +
>> +#ifndef _ROCKCHIP_DRM_DRV_H
>> +#define _ROCKCHIP_DRM_DRV_H
>> +
>> +#include <linux/module.h>
>> +#include <linux/component.h>
>> +
>> +#define ROCKCHIP_MAX_FB_BUFFER 3
>> +#define ROCKCHIP_MAX_CONNECTOR 2
>> +#define ROCKCHIP_MAX_CRTC 2
>> +
>> +struct drm_device;
>> +struct drm_connector;
>> +
>> +/*
>> + * Rockchip drm private structure.
>> + *
>> + * @crtc: array of enabled CRTCs, used to map from "pipe" to drm_crtc.
>> + * @num_pipe: number of pipes for this device.
>> + */
>> +struct rockchip_drm_private {
>> + struct drm_fb_helper *fb_helper;
>> + /*
>> + * created crtc object would be contained at this array and
>> + * this array is used to be aware of which crtc did it request vblank.
>> + */
>> + struct drm_crtc *crtc[ROCKCHIP_MAX_CRTC];
>> +
>> + unsigned int num_pipe;
>> +};
>> +
>> +int rockchip_drm_add_crtc(struct drm_device *drm, struct drm_crtc *crtc,
>> + struct device_node *port);
>> +void rockchip_drm_remove_crtc(struct drm_device *drm, int pipe);
>> +struct drm_crtc *rockchip_drm_find_crtc(struct drm_device *drm, int pipe);
>> +int rockchip_drm_encoder_get_mux_id(struct device_node *node,
>> + struct drm_encoder *encoder);
>> +int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type);
>> +int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe);
>> +void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe);
>> +int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
>> + struct device *dev);
>> +void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
>> + struct device *dev);
>> +
>> +#endif /* _ROCKCHIP_DRM_DRV_H_ */
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
>> new file mode 100644
>> index 0000000..482f7b8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
>> @@ -0,0 +1,201 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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 <linux/kernel.h>
>> +#include <drm/drm.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_fb_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +
>> +#include "rockchip_drm_drv.h"
>> +#include "rockchip_drm_gem.h"
>> +
>> +#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
>> +
>> +struct rockchip_drm_fb {
>> + struct drm_framebuffer fb;
>> + struct drm_gem_object *obj[ROCKCHIP_MAX_FB_BUFFER];
>> +};
>> +
>> +struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
>> + unsigned int plane)
>> +{
>> + struct rockchip_drm_fb *rk_fb = to_rockchip_fb(fb);
>> +
>> + if (plane >= ROCKCHIP_MAX_FB_BUFFER)
>> + return NULL;
>> +
>> + return rk_fb->obj[plane];
>> +}
>> +
>> +static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
>> +{
>> + struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
>> + struct drm_gem_object *obj;
>> + int i;
>> +
>> + for (i = 0; i < ROCKCHIP_MAX_FB_BUFFER; i++) {
>> + obj = rockchip_fb->obj[i];
>> + if (obj)
>> + drm_gem_object_unreference_unlocked(obj);
>> + }
>> +
>> + drm_framebuffer_cleanup(fb);
>> + kfree(rockchip_fb);
>> +}
>> +
>> +static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb,
>> + struct drm_file *file_priv,
>> + unsigned int *handle)
>> +{
>> + struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
>> +
>> + return drm_gem_handle_create(file_priv,
>> + rockchip_fb->obj[0], handle);
>> +}
>> +
>> +static struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
>> + .destroy = rockchip_drm_fb_destroy,
>> + .create_handle = rockchip_drm_fb_create_handle,
>> +};
>> +
>> +static struct rockchip_drm_fb *
>> +rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd,
>> + struct drm_gem_object **obj, unsigned int num_planes)
>> +{
>> + struct rockchip_drm_fb *rockchip_fb;
>> + int ret;
>> + int i;
>> +
>> + rockchip_fb = kzalloc(sizeof(*rockchip_fb), GFP_KERNEL);
>> + if (!rockchip_fb)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + drm_helper_mode_fill_fb_struct(&rockchip_fb->fb, mode_cmd);
>> +
>> + for (i = 0; i < num_planes; i++)
>> + rockchip_fb->obj[i] = obj[i];
>> +
>> + ret = drm_framebuffer_init(dev, &rockchip_fb->fb,
>> + &rockchip_drm_fb_funcs);
>> + if (ret) {
>> + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
>> + ret);
>> + kfree(rockchip_fb);
>> + return ERR_PTR(ret);
>> + }
>> +
>> + return rockchip_fb;
>> +}
>> +
>> +static struct drm_framebuffer *
>> +rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
>> + struct drm_mode_fb_cmd2 *mode_cmd)
>> +{
>> + struct rockchip_drm_fb *rockchip_fb;
>> + struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER];
>> + struct drm_gem_object *obj;
>> + unsigned int hsub;
>> + unsigned int vsub;
>> + int num_planes;
>> + int ret;
>> + int i;
>> +
>> + hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
>> + vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
>> + num_planes = min(drm_format_num_planes(mode_cmd->pixel_format),
>> + ROCKCHIP_MAX_FB_BUFFER);
>> +
>> + for (i = 0; i < num_planes; i++) {
>> + unsigned int width = mode_cmd->width / (i ? hsub : 1);
>> + unsigned int height = mode_cmd->height / (i ? vsub : 1);
>> + unsigned int min_size;
>> +
>> + obj = drm_gem_object_lookup(dev, file_priv,
>> + mode_cmd->handles[i]);
>> + if (!obj) {
>> + dev_err(dev->dev, "Failed to lookup GEM object\n");
>> + ret = -ENXIO;
>> + goto err_gem_object_unreference;
>> + }
>> +
>> + min_size = (height - 1) * mode_cmd->pitches[i] +
>> + mode_cmd->offsets[i] +
>> + width * drm_format_plane_cpp(mode_cmd->pixel_format, i);
>> +
>> + if (obj->size < min_size) {
>> + drm_gem_object_unreference_unlocked(obj);
>> + ret = -EINVAL;
>> + goto err_gem_object_unreference;
>> + }
>> + objs[i] = obj;
>> + }
>> +
>> + rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, objs, i);
>> + if (IS_ERR(rockchip_fb)) {
>> + ret = PTR_ERR(rockchip_fb);
>> + goto err_gem_object_unreference;
>> + }
>> +
>> + return &rockchip_fb->fb;
>> +
>> +err_gem_object_unreference:
>> + for (i--; i >= 0; i--)
>> + drm_gem_object_unreference_unlocked(objs[i]);
>> + return ERR_PTR(ret);
>> +}
>> +
>> +static void rockchip_drm_output_poll_changed(struct drm_device *dev)
>> +{
>> + struct rockchip_drm_private *private = dev->dev_private;
>> + struct drm_fb_helper *fb_helper = private->fb_helper;
>> +
>> + if (fb_helper)
>> + drm_fb_helper_hotplug_event(fb_helper);
>> +}
>> +
>> +static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
>> + .fb_create = rockchip_user_fb_create,
>> + .output_poll_changed = rockchip_drm_output_poll_changed,
>> +};
>> +
>> +struct drm_framebuffer *
>> +rockchip_drm_framebuffer_init(struct drm_device *dev,
>> + struct drm_mode_fb_cmd2 *mode_cmd,
>> + struct drm_gem_object *obj)
>> +{
>> + struct rockchip_drm_fb *rockchip_fb;
>> +
>> + rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1);
>> + if (IS_ERR(rockchip_fb))
>> + return NULL;
>> +
>> + return &rockchip_fb->fb;
>> +}
>> +
>> +void rockchip_drm_mode_config_init(struct drm_device *dev)
>> +{
>> + dev->mode_config.min_width = 0;
>> + dev->mode_config.min_height = 0;
>> +
>> + /*
>> + * set max width and height as default value(4096x4096).
>> + * this value would be used to check framebuffer size limitation
>> + * at drm_mode_addfb().
>> + */
>> + dev->mode_config.max_width = 4096;
>> + dev->mode_config.max_height = 4096;
>> +
>> + dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
>> +}
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
>> new file mode 100644
>> index 0000000..09574d4
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
>> @@ -0,0 +1,28 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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.
>> + */
>> +
>> +#ifndef _ROCKCHIP_DRM_FB_H
>> +#define _ROCKCHIP_DRM_FB_H
>> +
>> +struct drm_framebuffer *
>> +rockchip_drm_framebuffer_init(struct drm_device *dev,
>> + struct drm_mode_fb_cmd2 *mode_cmd,
>> + struct drm_gem_object *obj);
>> +void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
>> +
>> +void rockchip_drm_mode_config_init(struct drm_device *dev);
>> +
>> +struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
>> + unsigned int plane);
>> +#endif /* _ROCKCHIP_DRM_FB_H */
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
>> new file mode 100644
>> index 0000000..ae563f5
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
>> @@ -0,0 +1,230 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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/drm.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_fb_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +
>> +#include "rockchip_drm_drv.h"
>> +#include "rockchip_drm_gem.h"
>> +#include "rockchip_drm_fb.h"
>> +
>> +#define PREFERRED_BPP 32
>> +#define to_rockchip_fbdev(x) container_of(x, struct rockchip_fbdev, helper)
>> +
>> +struct rockchip_fbdev {
>> + struct drm_fb_helper helper;
>> + struct drm_gem_object *bo;
>> +};
>> +
>> +static int rockchip_fbdev_mmap(struct fb_info *info,
>> + struct vm_area_struct *vma)
>> +{
>> + struct drm_fb_helper *helper = info->par;
>> + struct rockchip_fbdev *fbdev = to_rockchip_fbdev(helper);
>> +
>> + return rockchip_gem_mmap(fbdev->bo, vma);
>> +}
>> +
>> +static struct fb_ops rockchip_drm_fbdev_ops = {
>> + .owner = THIS_MODULE,
>> + .fb_mmap = rockchip_fbdev_mmap,
>> + .fb_fillrect = cfb_fillrect,
>> + .fb_copyarea = cfb_copyarea,
>> + .fb_imageblit = cfb_imageblit,
>> + .fb_check_var = drm_fb_helper_check_var,
>> + .fb_set_par = drm_fb_helper_set_par,
>> + .fb_blank = drm_fb_helper_blank,
>> + .fb_pan_display = drm_fb_helper_pan_display,
>> + .fb_setcmap = drm_fb_helper_setcmap,
>> +};
>> +
>> +static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
>> + struct drm_fb_helper_surface_size *sizes)
>> +{
>> + struct rockchip_fbdev *fbdev = to_rockchip_fbdev(helper);
>> + struct drm_mode_fb_cmd2 mode_cmd = { 0 };
>> + struct drm_device *dev = helper->dev;
>> + struct rockchip_gem_object *rk_obj;
>> + struct drm_framebuffer *fb;
>> + unsigned int bytes_per_pixel;
>> + unsigned long offset;
>> + struct fb_info *fbi;
>> + size_t size;
>> + int ret;
>> +
>> + bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
>> +
>> + mode_cmd.width = sizes->surface_width;
>> + mode_cmd.height = sizes->surface_height;
>> + mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
>> + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
>> + sizes->surface_depth);
>> +
>> + size = mode_cmd.pitches[0] * mode_cmd.height;
>> +
>> + rk_obj = rockchip_gem_create_object(dev, size);
>> + if (IS_ERR(rk_obj))
>> + return -ENOMEM;
>> +
>> + fbdev->bo = &rk_obj->base;
>> +
>> + fbi = framebuffer_alloc(0, dev->dev);
>> + if (!fbi) {
>> + dev_err(dev->dev, "Failed to allocate framebuffer info.\n");
>> + ret = -ENOMEM;
>> + goto err_rockchip_gem_free_object;
>> + }
>> +
>> + helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd, fbdev->bo);
>> + if (IS_ERR(helper->fb)) {
>> + dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
>> + ret = PTR_ERR(helper->fb);
>> + goto err_framebuffer_release;
>> + }
>> +
>> + helper->fbdev = fbi;
>> +
>> + fbi->par = helper;
>> + fbi->flags = FBINFO_FLAG_DEFAULT;
>> + fbi->fbops = &rockchip_drm_fbdev_ops;
>> +
>> + ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
>> + if (ret) {
>> + dev_err(dev->dev, "Failed to allocate color map.\n");
>> + goto err_drm_framebuffer_unref;
>> + }
>> +
>> + fb = helper->fb;
>> + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
>> + drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
>> +
>> + offset = fbi->var.xoffset * bytes_per_pixel;
>> + offset += fbi->var.yoffset * fb->pitches[0];
>> +
>> + dev->mode_config.fb_base = 0;
>> + fbi->screen_base = rk_obj->kvaddr + offset;
>> + fbi->screen_size = rk_obj->base.size;
>> + fbi->fix.smem_len = rk_obj->base.size;
>> +
>> + DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%d\n",
>> + fb->width, fb->height, fb->depth, rk_obj->kvaddr,
>> + offset, size);
>> + return 0;
>> +
>> +err_drm_framebuffer_unref:
>> + drm_framebuffer_unreference(helper->fb);
>> +err_framebuffer_release:
>> + framebuffer_release(fbi);
>> +err_rockchip_gem_free_object:
>> + rockchip_gem_free_object(&rk_obj->base);
>> + return ret;
>> +}
>> +
>> +static struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
>> + .fb_probe = rockchip_drm_fbdev_create,
>> +};
>> +
>> +int rockchip_drm_fbdev_init(struct drm_device *dev)
>> +{
>> + struct rockchip_drm_private *private = dev->dev_private;
>> + struct rockchip_fbdev *fbdev;
>> + struct drm_fb_helper *helper;
>> + unsigned int num_crtc;
>> + int ret;
>> +
>> + if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
>> + return -EINVAL;
>> +
>> + if (private->fb_helper) {
>> + DRM_ERROR("no allow to reinit fbdev\n");
>> + return -EINVAL;
>> + }
>> +
>> + num_crtc = dev->mode_config.num_crtc;
>> +
>> + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
> Can you just embed rockchip_fbdev in struct rockchip_drm_private...
> Or, just add its two fields to rockchip_drm_private:
>
> struct drm_fb_helper fbdev_helper;
> struct drm_gem_object *fbdev_bo;
>
> It would eliminate some of the complexity in _fbdev_init, and especially in
> _fbdev_fini.
OK.
>
>> + if (!fbdev)
>> + return -ENOMEM;
>> +
>> + fbdev->helper.funcs = &rockchip_drm_fb_helper_funcs;
>> + helper = &fbdev->helper;
>> +
>> + ret = drm_fb_helper_init(dev, helper, num_crtc, ROCKCHIP_MAX_CONNECTOR);
>> + if (ret < 0) {
>> + dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
>> + goto err_free;
>> + }
>> +
>> + ret = drm_fb_helper_single_add_all_connectors(helper);
>> + if (ret < 0) {
>> + dev_err(dev->dev, "Failed to add connectors.\n");
>> + goto err_drm_fb_helper_fini;
>> + }
>> +
>> + /* disable all the possible outputs/crtcs before entering KMS mode */
>> + drm_helper_disable_unused_functions(dev);
>> +
>> + ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
>> + if (ret < 0) {
>> + dev_err(dev->dev, "Failed to set initial hw configuration.\n");
>> + goto err_drm_fb_helper_fini;
>> + }
>> +
>> + private->fb_helper = helper;
>> +
>> + return 0;
>> +
>> +err_drm_fb_helper_fini:
>> + drm_fb_helper_fini(helper);
>> +err_free:
>> + kfree(fbdev);
>> + return ret;
>> +}
>> +
>> +void rockchip_drm_fbdev_fini(struct drm_device *dev)
>> +{
>> + struct rockchip_drm_private *private = dev->dev_private;
>> + struct drm_fb_helper *helper;
>> + struct rockchip_fbdev *fbdev;
>> +
>> + if (!private || !private->fb_helper)
>> + return;
> Can these ever be NULL here?
maybe can't be NULL if fbdev_init()/fbdev_fini() in pairs.
>> +
>> + helper = private->fb_helper;
>> + fbdev = to_rockchip_fbdev(helper);
>> +
>> + if (helper->fbdev) {
>> + struct fb_info *info;
>> + int ret;
>> +
>> + info = helper->fbdev;
>> + ret = unregister_framebuffer(info);
>> + if (ret < 0)
>> + DRM_DEBUG_KMS("failed unregister_framebuffer()\n");
>> +
>> + if (info->cmap.len)
>> + fb_dealloc_cmap(&info->cmap);
>> +
>> + framebuffer_release(info);
>> + }
>> +
>> + if (helper->fb)
>> + drm_framebuffer_unreference(helper->fb);
>> +
>> + drm_fb_helper_fini(helper);
>> + kfree(fbdev);
>> + private->fb_helper = NULL;
>> +}
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
>> new file mode 100644
>> index 0000000..5edcf6a
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
>> @@ -0,0 +1,20 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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.
>> + */
>> +
>> +#ifndef _ROCKCHIP_DRM_FBDEV_H
>> +#define _ROCKCHIP_DRM_FBDEV_H
>> +
>> +int rockchip_drm_fbdev_init(struct drm_device *dev);
>> +
>> +#endif /* _ROCKCHIP_DRM_FBDEV_H */
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
>> new file mode 100644
>> index 0000000..5563dff
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
>> @@ -0,0 +1,345 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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/drm.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_vma_manager.h>
>> +
>> +#include <linux/anon_inodes.h>
>> +#include <linux/dma-attrs.h>
>> +
>> +#include "rockchip_drm_drv.h"
>> +#include "rockchip_drm_gem.h"
>> +
>> +static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj)
>> +{
>> + struct drm_gem_object *obj = &rk_obj->base;
>> + struct drm_device *drm = obj->dev;
>> +
>> + init_dma_attrs(&rk_obj->dma_attrs);
>> + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &rk_obj->dma_attrs);
>> +
>> + /* TODO(djkurtz): Use DMA_ATTR_NO_KERNEL_MAPPING except for fbdev */
>> + rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
>> + &rk_obj->dma_addr, GFP_KERNEL,
>> + &rk_obj->dma_attrs);
>> + if (IS_ERR(rk_obj->kvaddr)) {
>> + int ret = PTR_ERR(rk_obj->kvaddr);
>> +
>> + DRM_ERROR("failed to allocate %#x byte dma buffer, %d",
>> + obj->size, ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void rockchip_gem_free_buf(struct rockchip_gem_object *rk_obj)
>> +{
>> + struct drm_gem_object *obj = &rk_obj->base;
>> + struct drm_device *drm = obj->dev;
>> +
>> + dma_free_attrs(drm->dev, obj->size, rk_obj->kvaddr, rk_obj->dma_addr,
>> + &rk_obj->dma_attrs);
>> +}
>> +
>> +/* drm driver mmap file operations */
>> +int rockchip_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
>> +{
>> + struct drm_file *priv = filp->private_data;
>> + struct drm_device *dev = priv->minor->dev;
>> + struct drm_gem_object *obj;
>> + struct drm_vma_offset_node *node;
>> + int ret;
>> +
>> + if (drm_device_is_unplugged(dev))
>> + return -ENODEV;
>> +
>> + mutex_lock(&dev->struct_mutex);
>> +
>> + node = drm_vma_offset_exact_lookup(dev->vma_offset_manager,
>> + vma->vm_pgoff,
>> + vma_pages(vma));
>> + if (!node) {
>> + mutex_unlock(&dev->struct_mutex);
>> + DRM_ERROR("failed to find vma node.\n");
>> + return -EINVAL;
>> + } else if (!drm_vma_node_is_allowed(node, filp)) {
>> + mutex_unlock(&dev->struct_mutex);
>> + return -EACCES;
>> + }
>> +
>> + obj = container_of(node, struct drm_gem_object, vma_node);
>> + ret = rockchip_gem_mmap(obj, vma);
>> +
>> + mutex_unlock(&dev->struct_mutex);
>> +
>> + return ret;
>> +}
>> +
>> +int rockchip_drm_gem_mmap_buffer(struct file *filp,
>> + struct vm_area_struct *vma)
>> +{
>> + struct drm_gem_object *obj = filp->private_data;
>> +
>> + return rockchip_gem_mmap(obj, vma);
>> +}
>> +
>> +static const struct file_operations rockchip_drm_gem_fops = {
>> + .mmap = rockchip_drm_gem_mmap_buffer,
>> +};
>> +
>> +struct rockchip_gem_object *
>> + rockchip_gem_create_object(struct drm_device *drm, unsigned int size)
>> +{
>> + struct rockchip_gem_object *rk_obj;
>> + struct drm_gem_object *obj;
>> + struct file *filp;
>> + int ret;
>> +
>> + size = round_up(size, PAGE_SIZE);
>> +
>> + rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
>> + if (!rk_obj)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + obj = &rk_obj->base;
>> +
>> + drm_gem_private_object_init(drm, obj, size);
>> +
>> + filp = anon_inode_getfile("rockchip_gem", &rockchip_drm_gem_fops,
>> + obj, 0);
>> + if (IS_ERR(filp)) {
>> + DRM_ERROR("failed to create anon file object.\n");
>> + ret = PTR_ERR(filp);
>> + goto err_free_rk_obj;
>> + }
>> + filp->f_mode = FMODE_READ | FMODE_WRITE;
>> + obj->filp = filp;
> I think this chunk goes away now, if we do not have custom gem mmap ioctl? an
> Along with rockchip_drm_gem_fops and rockchip_drm_gem_mmap_buffer.
>
>
Oh, yes, now this chunk is not in use, I will just let it go away.
>> +
>> + ret = drm_gem_create_mmap_offset(obj);
>> + if (ret)
>> + goto err_free_obj;
>> +
>> + ret = rockchip_gem_alloc_buf(rk_obj);
>> + if (ret)
>> + goto err_free_mmap_offset;
>> +
>> + return rk_obj;
>> +
>> +err_free_mmap_offset:
>> + drm_gem_free_mmap_offset(obj);
>> +err_free_obj:
>> + drm_gem_object_release(obj);
>> +err_free_rk_obj:
>> + kfree(rk_obj);
>> + return ERR_PTR(ret);
>> +}
>> +
>> +/*
>> + * rockchip_gem_free_object - (struct drm_driver)->gem_free_object callback
>> + * function
>> + */
>> +void rockchip_gem_free_object(struct drm_gem_object *obj)
>> +{
>> + struct rockchip_gem_object *rk_obj;
>> +
>> + drm_gem_free_mmap_offset(obj);
>> +
>> + rk_obj = to_rockchip_obj(obj);
>> +
>> + rockchip_gem_free_buf(rk_obj);
>> +
>> + drm_gem_object_release(obj);
>> +
>> + kfree(rk_obj);
>> +}
>> +
>> +int rockchip_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
> Nit: Can you move this function up just below rockchip_gem_free_buf.
> That would put these three dma_*_attrs() wrapper functions in one place.
OK
>> +{
>> + struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
>> + struct drm_device *drm = obj->dev;
>> + unsigned long vm_size;
>> +
>> + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
>> + vm_size = vma->vm_end - vma->vm_start;
>> +
>> + if (vm_size > obj->size)
>> + return -EINVAL;
>> +
>> + return dma_mmap_attrs(drm->dev, vma, rk_obj->kvaddr, rk_obj->dma_addr,
>> + obj->size, &rk_obj->dma_attrs);
>> +}
>> +
>> +/*
>> + * rockchip_gem_create_with_handle - allocate an object with the given
>> + * size and create a gem handle on it
>> + *
>> + * returns a struct rockchip_gem_object* on success or ERR_PTR values
>> + * on failure.
>> + */
>> +static struct rockchip_gem_object *
>> +rockchip_gem_create_with_handle(struct drm_file *file_priv,
>> + struct drm_device *drm, unsigned int size,
>> + unsigned int *handle)
>> +{
>> + struct rockchip_gem_object *rk_obj;
>> + struct drm_gem_object *obj;
>> + int ret;
>> +
>> + rk_obj = rockchip_gem_create_object(drm, size);
>> + if (IS_ERR(rk_obj))
>> + return NULL;
> Should this be:
> return ERR_CAST(rk_obj);
OK.
>> +
>> + obj = &rk_obj->base;
>> +
>> + /*
>> + * allocate a id of idr table where the obj is registered
>> + * and handle has the id what user can see.
>> + */
>> + ret = drm_gem_handle_create(file_priv, obj, handle);
>> + if (ret)
>> + goto err_handle_create;
>> +
>> + /* drop reference from allocate - handle holds it now. */
>> + drm_gem_object_unreference_unlocked(obj);
>> +
>> + return rk_obj;
>> +
>> +err_handle_create:
>> + rockchip_gem_free_object(obj);
>> +
>> + return ERR_PTR(ret);
>> +}
>> +
>> +int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
>> + struct drm_device *dev, uint32_t handle,
>> + uint64_t *offset)
>> +{
>> + struct drm_gem_object *obj;
>> + int ret = 0;
> Nit: No = 0;
>
> (I am a fan of letting the compiler tell us if we forget to initialize a
> return value in goto/error paths)
that is cool.
>> +
>> + mutex_lock(&dev->struct_mutex);
>> +
>> + /*
>> + * get offset of memory allocated for drm framebuffer.
>> + * - this callback would be called by user application
>> + * with DRM_IOCTL_MODE_MAP_DUMB command.
>> + */
> Just delete this comment that was copied from exynos.
OK.
>> +
>> + obj = drm_gem_object_lookup(dev, file_priv, handle);
>> + if (!obj) {
>> + DRM_ERROR("failed to lookup gem object.\n");
>> + ret = -EINVAL;
>> + goto unlock;
>> + }
>> +
>> + ret = drm_gem_create_mmap_offset(obj);
>> + if (ret)
>> + goto out;
>> +
>> + *offset = drm_vma_node_offset_addr(&obj->vma_node);
>> + DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset);
> Nit: print all 64 bits of offset
you are right, unsigned long is 32bit.
>> +
>> +out:
>> + drm_gem_object_unreference(obj);
>> +unlock:
>> + mutex_unlock(&dev->struct_mutex);
>> + return ret;
>> +}
>> +
>> +/*
>> + * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
>> + * function
>> + *
>> + * This aligns the pitch and size arguments to the minimum required. wrap
>> + * this into your own function if you need bigger alignment.
>> + */
>> +int rockchip_gem_dumb_create(struct drm_file *file_priv,
>> + struct drm_device *dev,
>> + struct drm_mode_create_dumb *args)
>> +{
>> + struct rockchip_gem_object *rk_obj;
>> + int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
>> +
>> + /*
>> + * align to 64 bytes since Mali requires it.
>> + */
>> + min_pitch = ((min_pitch + 63) / 64) * 64;
> ALIGN(min_pitch, 64);
OK.
>> +
>> + if (args->pitch < min_pitch)
>> + args->pitch = min_pitch;
>> +
>> + if (args->size < args->pitch * args->height)
>> + args->size = args->pitch * args->height;
>> +
>> + rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
>> + &args->handle);
>> +
>> + return PTR_ERR_OR_ZERO(rk_obj);
>> +}
>> +
>> +/*
>> + * Allocate a sg_table for this GEM object.
>> + * Note: Both the table's contents, and the sg_table itself must be freed by
>> + * the caller.
>> + * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
>> + */
>> +struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj)
>> +{
>> + struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
>> + struct drm_device *drm = obj->dev;
>> + struct sg_table *sgt = NULL;
> Do not init to NULL
>
OK.
>> + int ret;
>> +
>> + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
>> + if (!sgt)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + ret = dma_get_sgtable_attrs(drm->dev, sgt, rk_obj->kvaddr,
>> + rk_obj->dma_addr, obj->size,
>> + &rk_obj->dma_attrs);
>> + if (ret) {
>> + DRM_ERROR("failed to allocate sgt, %d\n", ret);
>> + kfree(sgt);
>> + return ERR_PTR(ret);
>> + }
>> +
>> + return sgt;
>> +}
>> +
>> +void *rockchip_gem_prime_vmap(struct drm_gem_object *obj)
>> +{
>> + struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
>> +
>> + return rk_obj->kvaddr;
>> +}
>> +
>> +void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
>> +{
>> + /* Nothing to do */
>> +}
>> +
>> +int rockchip_gem_prime_mmap(struct drm_gem_object *obj,
>> + struct vm_area_struct *vma)
> I don't think this function is correct. I think we should use dma_mmap_attrs()?
> But, you can probably just remove it, and drop support for mmap'ing
> exported dma_bufs for now.
OK, I will drop it.
>> +{
>> + struct drm_device *dev = obj->dev;
>> + int ret;
>> +
>> + mutex_lock(&dev->struct_mutex);
>> + ret = drm_gem_mmap_obj(obj, obj->size, vma);
>> + mutex_unlock(&dev->struct_mutex);
>> +
>> + return ret;
>> +}
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.h b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
>> new file mode 100644
>> index 0000000..e37b921
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
>> @@ -0,0 +1,55 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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.
>> + */
>> +
>> +#ifndef _ROCKCHIP_DRM_GEM_H
>> +#define _ROCKCHIP_DRM_GEM_H
>> +
>> +#define to_rockchip_obj(x) container_of(x, struct rockchip_gem_object, base)
>> +
>> +struct rockchip_gem_object {
>> + struct drm_gem_object base;
>> + unsigned int flags;
>> +
>> + void *kvaddr;
>> + dma_addr_t dma_addr;
>> + struct dma_attrs dma_attrs;
>> +};
>> +
>> +struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj);
>> +struct drm_gem_object *
>> +rockchip_gem_prime_import_sg_table(struct drm_device *dev, size_t size,
>> + struct sg_table *sgt);
>> +void *rockchip_gem_prime_vmap(struct drm_gem_object *obj);
>> +void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
>> +int rockchip_gem_prime_mmap(struct drm_gem_object *obj,
>> + struct vm_area_struct *vma);
>> +
>> +/* drm driver mmap file operations */
>> +int rockchip_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
>> +
>> +/* mmap a gem object to userspace. */
>> +int rockchip_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
>> +
>> +struct rockchip_gem_object *
>> + rockchip_gem_create_object(struct drm_device *drm, unsigned int size);
>> +
>> +void rockchip_gem_free_object(struct drm_gem_object *obj);
>> +
>> +int rockchip_gem_dumb_create(struct drm_file *file_priv,
>> + struct drm_device *dev,
>> + struct drm_mode_create_dumb *args);
>> +int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
>> + struct drm_device *dev, uint32_t handle,
>> + uint64_t *offset);
>> +#endif /* _ROCKCHIP_DRM_GEM_H */
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
>> new file mode 100644
>> index 0000000..fe9d2b0
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
>> @@ -0,0 +1,1422 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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/drm.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_crtc_helper.h>
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/clk.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/component.h>
>> +
>> +#include <linux/reset.h>
>> +#include <linux/iommu.h>
>> +#include <linux/delay.h>
>> +
>> +#include <video/of_display_timing.h>
>> +#include <video/of_videomode.h>
>> +
>> +#include "rockchip_drm_drv.h"
>> +#include "rockchip_drm_fbdev.h"
>> +#include "rockchip_drm_gem.h"
>> +#include "rockchip_drm_fb.h"
>> +#include "rockchip_drm_vop.h"
>> +
>> +#define VOP_DEFAULT_FRAMERATE 60
>> +#define VOP_MAX_WIN_SUPPORT 5
>> +#define VOP_DEFAULT_PRIMARY 0
>> +#define VOP_DEFAULT_CURSOR 1
>> +#define VOP_REG(off, _mask, s) \
>> + {.offset = off, \
>> + .mask = _mask, \
>> + .shift = s,}
>> +
>> +#define __REG_SET(x, off, mask, shift, v) \
>> + vop_mask_write(x, off, (mask) << shift, (v) << shift)
>> +
>> +#define REG_SET(x, base, reg, v) \
>> + __REG_SET(x, base + reg.offset, reg.mask, reg.shift, v)
>> +
>> +#define VOP_WIN_SET(x, win, name, v) \
>> + REG_SET(x, win->base, win->phy->name, v)
>> +#define VOP_CTRL_SET(x, name, v) \
>> + REG_SET(x, 0, (x)->data->ctrl->name, v)
>> +
>> +#define VOP_WIN_GET_YRGBADDR(ctx, win) \
>> + vop_readl(ctx, win->base + win->phy->yrgb_mst.offset)
>> +
>> +#define to_vop_ctx(x) container_of(x, struct vop_context, crtc)
>> +#define to_rockchip_plane(x) container_of(x, struct rockchip_plane, base)
>> +
>> +struct rockchip_plane {
>> + int id;
>> + struct drm_plane base;
>> + const struct vop_win *win;
>> +
>> + uint32_t pending_yrgb_mst;
>> + struct drm_framebuffer *front_fb;
>> + struct drm_framebuffer *pending_fb;
>> + bool enabled;
>> +};
>> +
>> +struct vop_context {
>> + struct device *dev;
>> + struct drm_device *drm_dev;
>> + struct drm_crtc crtc;
>> + struct drm_pending_vblank_event *event;
>> + struct vop_driver *drv;
>> + unsigned int dpms;
>> + unsigned int win_mask;
>> + wait_queue_head_t wait_vsync_queue;
>> + atomic_t wait_vsync_event;
>> +
>> + struct workqueue_struct *vsync_wq;
>> + struct work_struct vsync_work;
>> +
>> + /* mutex vsync_ work */
>> + struct mutex vsync_mutex;
>> + bool vsync_work_pending;
>> +
>> + struct vop_driver_data *data;
>> +
>> + uint32_t *regsbak;
>> + void __iomem *regs;
>> +
>> + /* physical map length of vop register */
>> + uint32_t len;
>> +
>> + /* one time only one process allowed to config the register */
>> + spinlock_t reg_lock;
>> + /* lock vop irq reg */
>> + spinlock_t irq_lock;
>> +
>> + unsigned int irq;
>> +
>> + /* vop AHP clk */
>> + struct clk *hclk;
>> + /* vop dclk */
>> + struct clk *dclk;
>> + /* vop share memory frequency */
>> + struct clk *aclk;
>> +
>> + /* vop ahb reset */
>> + struct reset_control *ahb_rst;
>> + /* vop dclk reset */
>> + struct reset_control *dclk_rst;
>> + uint32_t pixclock;
> Where is pixclock used?
not use now.
>> +
>> + int pipe;
>> + bool clk_on;
>> +};
>> +
>> +enum vop_data_format {
>> + VOP_FMT_ARGB8888 = 0,
>> + VOP_FMT_RGB888,
>> + VOP_FMT_RGB565,
>> + VOP_FMT_YUV420SP = 4,
>> + VOP_FMT_YUV422SP,
>> + VOP_FMT_YUV444SP,
>> +};
>> +
>> +struct vop_reg_data {
>> + uint32_t offset;
>> + uint32_t value;
>> +};
>> +
>> +struct vop_reg {
>> + uint32_t offset;
>> + uint32_t shift;
>> + uint32_t mask;
>> +};
>> +
>> +struct vop_ctrl {
>> + struct vop_reg standby;
>> + struct vop_reg data_blank;
>> + struct vop_reg gate_en;
>> + struct vop_reg mmu_en;
>> + struct vop_reg rgb_en;
>> + struct vop_reg edp_en;
>> + struct vop_reg hdmi_en;
>> + struct vop_reg mipi_en;
>> + struct vop_reg out_mode;
>> + struct vop_reg dither_down;
>> + struct vop_reg dither_up;
>> + struct vop_reg pin_pol;
>> +
>> + struct vop_reg htotal_pw;
>> + struct vop_reg hact_st_end;
>> + struct vop_reg vtotal_pw;
>> + struct vop_reg vact_st_end;
>> + struct vop_reg hpost_st_end;
>> + struct vop_reg vpost_st_end;
>> +};
>> +
>> +struct vop_win_phy {
>> + const uint32_t *data_formats;
>> + uint32_t nformats;
>> +
>> + struct vop_reg enable;
>> + struct vop_reg format;
>> + struct vop_reg act_info;
>> + struct vop_reg dsp_info;
>> + struct vop_reg dsp_st;
>> + struct vop_reg yrgb_mst;
>> + struct vop_reg uv_mst;
>> + struct vop_reg yrgb_vir;
>> + struct vop_reg uv_vir;
>> +
>> + struct vop_reg dst_alpha_ctl;
>> + struct vop_reg src_alpha_ctl;
>> +};
>> +
>> +struct vop_win {
>> + uint32_t base;
>> + const struct vop_win_phy *phy;
>> +};
>> +
>> +struct vop_driver_data {
>> + const void *init_table;
>> + int table_size;
>> + const struct vop_ctrl *ctrl;
>> + const struct vop_win *win[VOP_MAX_WIN_SUPPORT];
>> +};
>> +
>> +static const uint32_t formats_01[] = {
>> + DRM_FORMAT_XRGB8888,
>> + DRM_FORMAT_ARGB8888,
>> + DRM_FORMAT_RGB888,
>> + DRM_FORMAT_RGB565,
>> + DRM_FORMAT_NV12,
>> + DRM_FORMAT_NV16,
>> + DRM_FORMAT_NV24,
>> +};
>> +
>> +static const uint32_t formats_234[] = {
>> + DRM_FORMAT_XRGB8888,
>> + DRM_FORMAT_ARGB8888,
>> + DRM_FORMAT_RGB888,
>> + DRM_FORMAT_RGB565,
>> +};
>> +
>> +static const struct vop_win_phy win01_data = {
>> + .data_formats = formats_01,
>> + .nformats = ARRAY_SIZE(formats_01),
>> + .enable = VOP_REG(WIN0_CTRL0, 0x1, 0),
>> + .format = VOP_REG(WIN0_CTRL0, 0x7, 1),
>> + .act_info = VOP_REG(WIN0_ACT_INFO, 0x1fff1fff, 0),
>> + .dsp_info = VOP_REG(WIN0_DSP_INFO, 0x1fff1fff, 0),
>> + .dsp_st = VOP_REG(WIN0_DSP_ST, 0x1fff1fff, 0),
>> + .yrgb_mst = VOP_REG(WIN0_YRGB_MST, 0xffffffff, 0),
>> + .uv_mst = VOP_REG(WIN0_CBR_MST, 0xffffffff, 0),
>> + .yrgb_vir = VOP_REG(WIN0_VIR, 0x3fff, 0),
>> + .uv_vir = VOP_REG(WIN0_VIR, 0x3fff, 16),
>> + .src_alpha_ctl = VOP_REG(WIN0_SRC_ALPHA_CTRL, 0xff, 0),
>> + .dst_alpha_ctl = VOP_REG(WIN0_DST_ALPHA_CTRL, 0xff, 0),
>> +};
>> +
>> +static const struct vop_win_phy win23_data = {
>> + .data_formats = formats_234,
>> + .nformats = ARRAY_SIZE(formats_234),
>> + .enable = VOP_REG(WIN2_CTRL0, 0x1, 0),
>> + .format = VOP_REG(WIN2_CTRL0, 0x7, 1),
>> + .dsp_info = VOP_REG(WIN2_DSP_INFO0, 0x0fff0fff, 0),
>> + .dsp_st = VOP_REG(WIN2_DSP_ST0, 0x1fff1fff, 0),
>> + .yrgb_mst = VOP_REG(WIN2_MST0, 0xffffffff, 0),
>> + .yrgb_vir = VOP_REG(WIN2_VIR0_1, 0x1fff, 0),
>> + .src_alpha_ctl = VOP_REG(WIN2_SRC_ALPHA_CTRL, 0xff, 0),
>> + .dst_alpha_ctl = VOP_REG(WIN2_DST_ALPHA_CTRL, 0xff, 0),
>> +};
>> +
>> +static const struct vop_win_phy cursor_data = {
>> + .data_formats = formats_234,
>> + .nformats = ARRAY_SIZE(formats_234),
>> + .enable = VOP_REG(HWC_CTRL0, 0x1, 0),
>> + .format = VOP_REG(HWC_CTRL0, 0x7, 1),
>> + .dsp_st = VOP_REG(HWC_DSP_ST, 0x1fff1fff, 0),
>> + .yrgb_mst = VOP_REG(HWC_MST, 0xffffffff, 0),
>> +};
>> +
>> +static const struct vop_win win0 = {
>> + .base = 0,
>> + .phy = &win01_data,
>> +};
>> +
>> +static const struct vop_win win1 = {
>> + .base = 0x40,
>> + .phy = &win01_data,
>> +};
>> +
>> +static const struct vop_win win2 = {
>> + .base = 0,
>> + .phy = &win23_data,
>> +};
>> +
>> +static const struct vop_win win3 = {
>> + .base = 0x50,
>> + .phy = &win23_data,
>> +};
>> +
>> +static const struct vop_win win_cursor = {
>> + .base = 0,
>> + .phy = &cursor_data,
>> +};
>> +
>> +static const struct vop_ctrl ctrl_data = {
>> + .standby = VOP_REG(SYS_CTRL, 0x1, 22),
>> + .gate_en = VOP_REG(SYS_CTRL, 0x1, 23),
>> + .mmu_en = VOP_REG(SYS_CTRL, 0x1, 20),
>> + .rgb_en = VOP_REG(SYS_CTRL, 0x1, 12),
>> + .hdmi_en = VOP_REG(SYS_CTRL, 0x1, 13),
>> + .edp_en = VOP_REG(SYS_CTRL, 0x1, 14),
>> + .mipi_en = VOP_REG(SYS_CTRL, 0x1, 15),
>> + .dither_down = VOP_REG(DSP_CTRL1, 0xf, 1),
>> + .dither_up = VOP_REG(DSP_CTRL1, 0x1, 6),
>> + .data_blank = VOP_REG(DSP_CTRL0, 0x1, 19),
>> + .out_mode = VOP_REG(DSP_CTRL0, 0xf, 0),
>> + .pin_pol = VOP_REG(DSP_CTRL0, 0xf, 4),
>> + .htotal_pw = VOP_REG(DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
>> + .hact_st_end = VOP_REG(DSP_HACT_ST_END, 0x1fff1fff, 0),
>> + .vtotal_pw = VOP_REG(DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
>> + .vact_st_end = VOP_REG(DSP_VACT_ST_END, 0x1fff1fff, 0),
>> + .hpost_st_end = VOP_REG(POST_DSP_HACT_INFO, 0x1fff1fff, 0),
>> + .vpost_st_end = VOP_REG(POST_DSP_VACT_INFO, 0x1fff1fff, 0),
>> +};
>> +
>> +static const struct vop_reg_data vop_init_reg_table[] = {
>> + {SYS_CTRL, 0x00801000},
>> + {DSP_CTRL0, 0x00000000},
>> + {WIN0_CTRL0, 0x00000080},
>> + {WIN1_CTRL0, 0x00000080},
>> +};
>> +
>> +static const struct vop_driver_data rockchip_rk3288_vop = {
>> + .init_table = vop_init_reg_table,
>> + .table_size = ARRAY_SIZE(vop_init_reg_table),
>> + .ctrl = &ctrl_data,
>> + .win[0] = &win0,
>> + .win[1] = &win1,
>> + .win[2] = &win2,
>> + .win[3] = &win3,
>> + .win[4] = &win_cursor,
>> +};
>> +
>> +static const struct of_device_id vop_driver_dt_match[] = {
>> + { .compatible = "rockchip,rk3288-vop",
>> + .data = (void *)&rockchip_rk3288_vop },
>> + {},
>> +};
>> +
>> +static inline void vop_writel(struct vop_context *ctx,
>> + uint32_t offset, uint32_t v)
>> +{
>> + writel(v, ctx->regs + offset);
> TODO: I *think* the following may be a useful optimization:
> When doing mode_set or update_plane, use writel_relaxed to write all of the
> shadowed mode/window configuration registers, and just use writel() to write
> vop_cfg_done. This will write all the configuration registers without barriers,
> but still ensure that all such registers are written before cfg_done.
>
> However, for most other register writes, you probably do want to always use
> writel().
I see, I will do it.
>> + ctx->regsbak[offset >> 2] = v;
>> +}
>> +
>> +static inline uint32_t vop_readl(struct vop_context *ctx, uint32_t offset)
>> +{
>> + return readl(ctx->regs + offset);
>> +}
>> +
>> +static inline void vop_cfg_done(struct vop_context *ctx)
>> +{
>> + writel(0x01, ctx->regs + REG_CFG_DONE);
>> +}
>> +
>> +static inline void vop_mask_write(struct vop_context *ctx,
>> + uint32_t offset, uint32_t mask, uint32_t v)
>> +{
>> + if (mask) {
>> + uint32_t cached_val = ctx->regsbak[offset >> 2];
>> +
>> + cached_val = (cached_val & ~mask) | v;
>> + writel(cached_val, ctx->regs + offset);
>> + ctx->regsbak[offset >> 2] = cached_val;
>> + }
>> +}
>> +
>> +static inline struct vop_driver_data *vop_get_driver_data(struct device *dev)
>> +{
>> + const struct of_device_id *of_id =
>> + of_match_device(vop_driver_dt_match, dev);
>> +
>> + return (struct vop_driver_data *)of_id->data;
>> +}
>> +
>> +static enum vop_data_format vop_convert_format(uint32_t format)
>> +{
>> + switch (format) {
>> + case DRM_FORMAT_XRGB8888:
>> + case DRM_FORMAT_ARGB8888:
>> + return VOP_FMT_ARGB8888;
>> + case DRM_FORMAT_RGB888:
>> + return VOP_FMT_RGB888;
>> + case DRM_FORMAT_RGB565:
>> + return VOP_FMT_RGB565;
>> + case DRM_FORMAT_NV12:
>> + return VOP_FMT_YUV420SP;
>> + case DRM_FORMAT_NV16:
>> + return VOP_FMT_YUV422SP;
>> + case DRM_FORMAT_NV24:
>> + return VOP_FMT_YUV444SP;
>> + default:
>> + DRM_ERROR("unsupport format[%08x]\n", format);
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +static bool is_alpha_support(uint32_t format)
>> +{
>> + switch (format) {
>> + case DRM_FORMAT_ARGB8888:
>> + return true;
>> + default:
>> + return false;
>> + }
>> +}
>> +
>> +/* TODO(djkurtz): move generic 'setup slave rk_iommu' code somewhere common */
>> +static int vop_iommu_init(struct vop_context *ctx)
>> +{
>> + struct device *dev = ctx->dev;
>> + struct device_node *np = dev->of_node;
>> + struct platform_device *pd;
>> + int count;
>> + int ret;
>> + struct of_phandle_args args;
>> +
>> + /* Each VOP must have exactly one iommu node, with no args */
>> + count = of_count_phandle_with_args(np, "iommus", "#iommu-cells");
>> + if (count != 1) {
>> + dev_err(dev, "of_count_phandle_with_args(%s) => %d\n",
>> + np->full_name, count);
>> + return -EINVAL;
>> + }
>> +
>> + ret = of_parse_phandle_with_args(np, "iommus", "#iommu-cells", 0,
>> + &args);
>> + if (ret) {
>> + dev_err(dev, "of_parse_phandle_with_args(%s) => %d\n",
>> + np->full_name, ret);
>> + return ret;
>> + }
>> + if (args.args_count != 0) {
>> + dev_err(dev, "incorrect number of iommu params found for %s (found %d, expected 0)\n",
>> + args.np->full_name, args.args_count);
>> + return -EINVAL;
>> + }
>> +
>> + pd = of_find_device_by_node(args.np);
>> + of_node_put(args.np);
>> + if (!pd) {
>> + dev_err(dev, "iommu %s not found\n", args.np->full_name);
>> + return -EPROBE_DEFER;
>> + }
>> +
>> + /* TODO(djkurtz): handle multiple slave iommus for a single master */
>> + dev->archdata.iommu = &pd->dev;
>> +
>> + ret = rockchip_drm_dma_attach_device(ctx->drm_dev, dev);
>> + if (ret) {
>> + dev_err(dev, "failed to attach to drm dma mapping, %d\n", ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void vop_iommu_fini(struct vop_context *ctx)
>> +{
>> + rockchip_drm_dma_detach_device(ctx->drm_dev, ctx->dev);
>> +}
>> +
>> +static int vop_clk_enable(struct vop_context *ctx)
>> +{
>> + int ret;
> if (ctx->clk_on)
> return 0;
OK.
>> +
>> + if (!ctx->clk_on) {
>> + ret = clk_enable(ctx->hclk);
>> + if (ret < 0) {
>> + dev_err(ctx->dev, "failed to enable hclk\n");
> I find it nice to print the errno, to:
>
> dev_err(ctx->dev, "failed to enable hclk, %d\n", ret);
>
>> + return ret;
>> + }
>> +
>> + ret = clk_enable(ctx->dclk);
>> + if (ret < 0) {
>> + dev_err(ctx->dev, "failed to enable dclk\n");
>> + goto err_dclk;
> goto err_disable_hclk;
>
>> + }
>> +
>> + ret = clk_enable(ctx->aclk);
>> + if (ret < 0) {
>> + dev_err(ctx->dev, "failed to enable aclk\n");
>> + goto err_aclk;;
> goto err_disable_dclk;
>
>> + }
>> + ctx->clk_on = true;
>> + }
>> +
>> + return ret;
> Hm, didn't the compiler complain about ret possibly being returned
> undefined here?
>
>> +err_aclk:
>> + clk_disable(ctx->aclk);
>> +err_dclk:
>> + clk_disable(ctx->dclk);
> err_disable_dclk:
> clk_disable(ctx->dclk);
> err_disable_hclk:
> clk_disable(ctx->hclk);
>
>> + return ret;
>> +}
>> +
>> +static void vop_clk_disable(struct vop_context *ctx)
>> +{
> if (ctx->clk_on)
> return;
>
>> + if (ctx->clk_on) {
>> + clk_disable(ctx->aclk);
>> + clk_disable(ctx->dclk);
>> + clk_disable(ctx->hclk);
>> + ctx->clk_on = false;
>> + }
>> +}
>> +
>> +static void vop_power_on(struct vop_context *ctx)
>> +{
>> + if (vop_clk_enable(ctx) < 0) {
>> + dev_err(ctx->dev, "failed to enable clks\n");
>> + return;
>> + }
>> +
>> + spin_lock(&ctx->reg_lock);
>> +
>> + VOP_CTRL_SET(ctx, standby, 0);
>> +
>> + spin_unlock(&ctx->reg_lock);
>> +}
>> +
>> +static void vop_power_off(struct vop_context *ctx)
>> +{
>> + spin_lock(&ctx->reg_lock);
>> +
>> + VOP_CTRL_SET(ctx, standby, 1);
>> +
>> + spin_unlock(&ctx->reg_lock);
>> +
>> + vop_clk_disable(ctx);
>> +}
>> +
>> +/*
>> + * @start: starting pixel in screen coordinates
>> + * @length: length of buffer "row" in pixels
>> + * @end: one pixel past end of buffer in screen coordinates
>> + * @last: width of screen, or one pixel past end of screen
>> + * @size: number of pixels of buffer to display on scree
>> + */
>> +static int rockchip_plane_get_size(int start, unsigned length, unsigned last)
>> +{
>> + int end = start + length;
>> + int size = 0;
>> +
>> + if (start <= 0) {
>> + if (end > 0)
>> + size = min_t(unsigned, end, last);
>> + } else if (start < last) {
>> + size = min_t(unsigned, last - start, length);
>> + }
>> +
>> + return size;
>> +}
>> +
>> +static int rockchip_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
>> + struct drm_framebuffer *fb, int crtc_x,
>> + int crtc_y, unsigned int crtc_w,
>> + unsigned int crtc_h, uint32_t src_x,
>> + uint32_t src_y, uint32_t src_w, uint32_t src_h)
>> +{
>> + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
>> + const struct vop_win *win = rockchip_plane->win;
>> + struct vop_context *ctx = to_vop_ctx(crtc);
>> + struct drm_gem_object *obj;
>> + struct rockchip_gem_object *rk_obj;
>> + unsigned long offset;
>> + unsigned int actual_w;
>> + unsigned int actual_h;
>> + unsigned int dsp_stx;
>> + unsigned int dsp_sty;
>> + unsigned int y_vir_stride;
>> + dma_addr_t yrgb_mst;
>> + enum vop_data_format format;
>> + uint32_t val;
>> + bool is_alpha;
>> +
>> + src_w >>= 16;
>> + src_h >>= 16;
>> + src_x >>= 16;
>> + src_y >>= 16;
>> +
>> + if (crtc_w != src_w || crtc_h != src_h)
>> + return -EINVAL;
> Can we use drm_plane_helper_check_update() here instead of doing our own checks?
OK.
>> +
>> + actual_w = rockchip_plane_get_size(crtc_x,
>> + crtc_w, crtc->mode.hdisplay);
>> + actual_h = rockchip_plane_get_size(crtc_y,
>> + crtc_h, crtc->mode.vdisplay);
>> + if (!actual_w || !actual_h)
>> + return -EINVAL;
>> +
>> + if (crtc_x < 0) {
>> + if (actual_w)
>> + src_x -= crtc_x;
>> + crtc_x = 0;
>> + }
>> +
>> + if (crtc_y < 0) {
>> + if (actual_h)
>> + src_y -= crtc_y;
>> + crtc_y = 0;
>> + }
>> +
>> + is_alpha = is_alpha_support(fb->pixel_format);
>> + format = vop_convert_format(fb->pixel_format);
>> + if (format < 0)
>> + return format;
>> +
>> + obj = rockchip_fb_get_gem_obj(fb, 0);
>> + if (!obj) {
>> + DRM_ERROR("fail to get rockchip gem object from framebuffer\n");
>> + return -EINVAL;
>> + }
>> +
>> + rk_obj = to_rockchip_obj(obj);
>> +
>> + yrgb_mst = rk_obj->dma_addr;
>> +
>> + dsp_stx = crtc_x + crtc->mode.htotal - crtc->mode.hsync_start;
>> + dsp_sty = crtc_y + crtc->mode.vtotal - crtc->mode.vsync_start;
>> +
>> + offset = src_x * (fb->bits_per_pixel >> 3);
>> + offset += src_y * fb->pitches[0];
>> +
>> + y_vir_stride = fb->pitches[0] / (fb->bits_per_pixel >> 3);
>> +
>> + spin_lock(&ctx->reg_lock);
>> +
>> + VOP_WIN_SET(ctx, win, format, format);
>> + VOP_WIN_SET(ctx, win, yrgb_vir, y_vir_stride);
>> + yrgb_mst += offset;
>> + VOP_WIN_SET(ctx, win, yrgb_mst, yrgb_mst);
>> + val = ((actual_h - 1) & 0xffff) << 16;
>> + val |= (actual_w - 1) & 0xffff;
>> + VOP_WIN_SET(ctx, win, act_info, val);
>> + VOP_WIN_SET(ctx, win, dsp_info, val);
>> + val = ((dsp_sty - 1) & 0xffff) << 16;
>> + val |= (dsp_stx - 1) & 0xffff;
>> + VOP_WIN_SET(ctx, win, dsp_st, val);
>> +
> It looks a bit funny to me, but according to the TRM I am looking at:
> act mask is 0x1fff
> dsp_info is 0x0fff
> dsp_st is 0x1fff
>
> But these would be better as macros:
>
> #define ACT_INFO_VAL(x) ((x - 1) & 0x1fff)
> #define DSP_INFO_VAL(x) ((x - 1) & 0x0fff)
> #define DSP_ST_VAL(x) ((x - 1) & 0x1fff)
>
> #define ACT_INFO(w, h) ((ACT_INFO_VAL(h) << 16) | (ACT_INFO_VAL(w))
> #define DSP_INFO(w, h) ((DSP_INFO_VAL(h) << 16) | (DSP_INFO_VAL(w))
> #define DSP_INFO(x, y) ((DSP_ST_VAL(y) << 16) | (DSP_ST_VAL(x))
At here, I only want to make sure width at low 16bit, height at high 16bit.
and you said act mask is 0x1fff, this mask would do by VOP_WIN_SET.
VOP_WIN_SET use vop_mask_write, and the mask define from win_data
static const struct vop_win_phy win01_data = {
...
.act_info = VOP_REG(WIN0_ACT_INFO, 0x1fff1fff, 0),
}
>> + if (is_alpha) {
>> + VOP_WIN_SET(ctx, win, dst_alpha_ctl,
>> + DST_FACTOR_M0(ALPHA_SRC_INVERSE));
>> + val = SRC_ALPHA_EN(1) | SRC_COLOR_M0(ALPHA_SRC_PRE_MUL) |
>> + SRC_ALPHA_M0(ALPHA_STRAIGHT) |
>> + SRC_BLEND_M0(ALPHA_PER_PIX) |
>> + SRC_ALPHA_CAL_M0(ALPHA_NO_SATURATION) |
>> + SRC_FACTOR_M0(ALPHA_ONE);
>> + VOP_WIN_SET(ctx, win, src_alpha_ctl, val);
>> + } else {
>> + VOP_WIN_SET(ctx, win, src_alpha_ctl, SRC_ALPHA_EN(0));
>> + }
>> +
>> + VOP_WIN_SET(ctx, win, enable, 1);
>> +
>> + spin_unlock(&ctx->reg_lock);
>> +
>> + mutex_lock(&ctx->vsync_mutex);
>> +
>> + /*
>> + * Because the buffer set to vop take effect at frame start time,
>> + * we need make sure old buffer is not in use before we release
>> + * it.
>> + * reference the framebuffer, and unference it when it swap out of vop.
>> + */
>> + if (fb != rockchip_plane->front_fb) {
>> + drm_framebuffer_reference(fb);
>> + if (rockchip_plane->pending_fb)
>> + drm_framebuffer_unreference(rockchip_plane->pending_fb);
>> + rockchip_plane->pending_fb = fb;
>> + rockchip_plane->pending_yrgb_mst = yrgb_mst;
>> + ctx->vsync_work_pending = true;
>> + }
>> + rockchip_plane->enabled = true;
>> +
>> + mutex_unlock(&ctx->vsync_mutex);
>> +
>> + spin_lock(&ctx->reg_lock);
>> + vop_cfg_done(ctx);
>> + spin_unlock(&ctx->reg_lock);
>> +
>> + return 0;
>> +}
>> +
>> +#define rockchip_update_plane_helper(plane, crtc, fb, x, y) \
>> + rockchip_update_plane(plane, crtc, fb, 0, 0, \
>> + (fb)->width, (fb)->height, \
>> + (x) << 16, (y) << 16, \
>> + (fb)->width << 16, (fb)->height << 16)
> (1) No. This is using the framebuffer size. We should be using the
> crtc's mode's
> dimensions to display the primary plane.
> (2) Also, please make this an inline function named
> rockchip_update_primary_plane()
OK, I will do it.
>> +
>> +static int rockchip_disable_plane(struct drm_plane *plane)
>> +{
>> + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
>> + const struct vop_win *win = rockchip_plane->win;
>> + struct vop_context *ctx;
>> +
>> + if (!plane->crtc)
>> + return 0;
>> +
>> + ctx = to_vop_ctx(plane->crtc);
>> + spin_lock(&ctx->reg_lock);
>> +
>> + VOP_WIN_SET(ctx, win, enable, 0);
>> + vop_cfg_done(ctx);
>> +
>> + spin_unlock(&ctx->reg_lock);
>> +
>> + mutex_lock(&ctx->vsync_mutex);
>> +
>> + /*
>> + * clear the pending framebuffer and set vsync_work_pending true,
>> + * so that the framebuffer will unref at the next vblank.
>> + */
>> + if (rockchip_plane->pending_fb) {
>> + drm_framebuffer_unreference(rockchip_plane->pending_fb);
>> + rockchip_plane->pending_fb = NULL;
>> + }
>> +
>> + rockchip_plane->enabled = false;
>> + ctx->vsync_work_pending = true;
>> +
>> + mutex_unlock(&ctx->vsync_mutex);
>> +
>> + return 0;
>> +}
>> +
>> +static void rockchip_plane_destroy(struct drm_plane *plane)
>> +{
>> + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane);
>> + struct vop_context *ctx = to_vop_ctx(plane->crtc);
>> +
>> + rockchip_disable_plane(plane);
>> + drm_plane_cleanup(plane);
>> + ctx->win_mask &= ~(1 << rockchip_plane->id);
>> + kfree(rockchip_plane);
>> +}
>> +
>> +static const struct drm_plane_funcs rockchip_plane_funcs = {
>> + .update_plane = rockchip_update_plane,
>> + .disable_plane = rockchip_disable_plane,
>> + .destroy = rockchip_plane_destroy,
>> +};
>> +
>> +static struct drm_plane *rockchip_plane_init(struct vop_context *ctx,
>> + unsigned long possible_crtcs,
>> + enum drm_plane_type type,
>> + int index)
>> +{
>> + struct rockchip_plane *rockchip_plane;
>> + struct vop_driver_data *vop_data = ctx->data;
>> + const struct vop_win *win;
>> + int err;
>> +
>> + if (index >= VOP_MAX_WIN_SUPPORT)
>> + return ERR_PTR(-EINVAL);
>> +
>> + rockchip_plane = kzalloc(sizeof(*rockchip_plane), GFP_KERNEL);
>> + if (!rockchip_plane)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + win = vop_data->win[index];
>> + ctx->win_mask |= (1 << index);
>> + rockchip_plane->id = index;
>> + rockchip_plane->win = win;
>> +
>> + err = drm_universal_plane_init(ctx->drm_dev, &rockchip_plane->base,
>> + possible_crtcs, &rockchip_plane_funcs,
>> + win->phy->data_formats,
>> + win->phy->nformats, type);
>> + if (err) {
>> + DRM_ERROR("failed to initialize plane\n");
>> + kfree(rockchip_plane);
>> + return ERR_PTR(err);
>> + }
>> +
>> + return &rockchip_plane->base;
>> +}
>> +
>> +int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type)
>> +{
>> + struct vop_context *ctx = to_vop_ctx(crtc);
>> +
>> + if (ctx->dpms != DRM_MODE_DPMS_ON)
>> + return -EPERM;
>> +
>> + switch (connector_type) {
>> + case DRM_MODE_CONNECTOR_LVDS:
>> + VOP_CTRL_SET(ctx, rgb_en, 1);
>> + VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_P888);
>> + break;
>> + case DRM_MODE_CONNECTOR_eDP:
>> + VOP_CTRL_SET(ctx, edp_en, 1);
>> + VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_AAAA);
>> + break;
>> + case DRM_MODE_CONNECTOR_HDMIA:
>> + VOP_CTRL_SET(ctx, out_mode, ROCKCHIP_OUT_MODE_AAAA);
>> + VOP_CTRL_SET(ctx, hdmi_en, 1);
>> + break;
>> + default:
>> + DRM_ERROR("unsupport connector_type[%d]\n", connector_type);
>> + return -EINVAL;
>> + };
>> +
>> + return 0;
>> +}
>> +
>> +int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe)
>> +{
>> + struct vop_context *ctx = to_vop_ctx(rockchip_drm_find_crtc(dev, pipe));
>> + unsigned long flags;
>> +
>> + if (ctx->dpms != DRM_MODE_DPMS_ON)
>> + return -EPERM;
>> +
>> + spin_lock_irqsave(&ctx->irq_lock, flags);
>> +
>> + vop_mask_write(ctx, INTR_CTRL0, LINE_FLAG_INTR_MASK,
>> + LINE_FLAG_INTR_EN(1));
>> +
>> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
>> +
>> + return 0;
>> +}
>> +
>> +void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe)
>> +{
>> + struct vop_context *ctx = to_vop_ctx(rockchip_drm_find_crtc(dev, pipe));
>> + unsigned long flags;
>> +
>> + if (ctx->dpms != DRM_MODE_DPMS_ON)
>> + return;
>> + spin_lock_irqsave(&ctx->irq_lock, flags);
>> + vop_mask_write(ctx, INTR_CTRL0, LINE_FLAG_INTR_MASK,
>> + LINE_FLAG_INTR_EN(0));
>> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
>> +}
>> +
>> +static void rockchip_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
>> +{
>> + struct vop_context *ctx = to_vop_ctx(crtc);
>> +
>> + DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
>> +
>> + if (ctx->dpms == mode) {
>> + DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
>> + return;
>> + }
>> + if (mode > DRM_MODE_DPMS_ON) {
>> + /* wait for the completion of page flip. */
>> + if (!wait_event_timeout(ctx->wait_vsync_queue,
>> + !atomic_read(&ctx->wait_vsync_event),
>> + HZ/20))
>> + DRM_DEBUG_KMS("vblank wait timed out.\n");
>> + drm_vblank_off(crtc->dev, ctx->pipe);
>> + }
>> +
>> + switch (mode) {
>> + case DRM_MODE_DPMS_ON:
>> + vop_power_on(ctx);
>> + break;
>> + case DRM_MODE_DPMS_STANDBY:
>> + case DRM_MODE_DPMS_SUSPEND:
>> + case DRM_MODE_DPMS_OFF:
>> + vop_power_off(ctx);
>> + break;
>> + default:
>> + DRM_DEBUG_KMS("unspecified mode %d\n", mode);
>> + break;
>> + }
>> +
>> + ctx->dpms = mode;
>> +}
>> +
>> +static void rockchip_drm_crtc_prepare(struct drm_crtc *crtc)
>> +{
>> + rockchip_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
>> +}
>> +
>> +static bool rockchip_drm_crtc_mode_fixup(struct drm_crtc *crtc,
>> + const struct drm_display_mode *mode,
>> + struct drm_display_mode *adjusted_mode)
>> +{
>> + if (adjusted_mode->htotal == 0 || adjusted_mode->vtotal == 0)
>> + return false;
>> +
>> + return true;
>> +}
>> +
>> +static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>> + struct drm_framebuffer *old_fb);
>> +
>> +static int rockchip_drm_crtc_mode_set(struct drm_crtc *crtc,
>> + struct drm_display_mode *mode,
>> + struct drm_display_mode *adjusted_mode,
>> + int x, int y,
>> + struct drm_framebuffer *fb)
>> +{
>> + struct vop_context *ctx = to_vop_ctx(crtc);
>> + u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
>> + u16 hdisplay = adjusted_mode->hdisplay;
>> + u16 htotal = adjusted_mode->htotal;
>> + u16 hact_st = adjusted_mode->htotal - adjusted_mode->hsync_start;
>> + u16 hact_end = hact_st + hdisplay;
>> + u16 vdisplay = adjusted_mode->vdisplay;
>> + u16 vtotal = adjusted_mode->vtotal;
>> + u16 vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
>> + u16 vact_st = adjusted_mode->vtotal - adjusted_mode->vsync_start;
>> + u16 vact_end = vact_st + vdisplay;
>> + unsigned long flags;
>> + int ret;
>> + uint32_t val;
>> +
>> + clk_disable(ctx->dclk);
>> +
>> + ret = rockchip_drm_crtc_mode_set_base(crtc, x, y, fb);
>> + if (ret)
>> + return ret;
>> +
>> + val = 0x8;
>> + val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0;
>> + val |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? (1 << 1) : 0;
>> + VOP_CTRL_SET(ctx, pin_pol, val);
>> +
>> + VOP_CTRL_SET(ctx, htotal_pw, (htotal << 16) | hsync_len);
>> + val = hact_st << 16;
>> + val |= hact_end;
>> + VOP_CTRL_SET(ctx, hact_st_end, val);
>> + VOP_CTRL_SET(ctx, hpost_st_end, val);
>> +
>> + VOP_CTRL_SET(ctx, vtotal_pw, (vtotal << 16) | vsync_len);
>> + val = vact_st << 16;
>> + val |= vact_end;
>> + VOP_CTRL_SET(ctx, vact_st_end, val);
>> + VOP_CTRL_SET(ctx, vpost_st_end, val);
>> +
>> + spin_lock_irqsave(&ctx->irq_lock, flags);
>> +
>> + vop_mask_write(ctx, INTR_CTRL0, DSP_LINE_NUM_MASK,
>> + DSP_LINE_NUM(vact_end));
>> +
>> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
>> +
>> + /*
>> + * do dclk_reset, let all config take affect, so new clk will safely
>> + * run in correct frame.
>> + */
>> + reset_control_assert(ctx->dclk_rst);
>> + usleep_range(10, 20);
>> + reset_control_deassert(ctx->dclk_rst);
>> +
>> + clk_set_rate(ctx->dclk, adjusted_mode->clock * 1000);
>> + clk_enable(ctx->dclk);
>> +
>> + return 0;
>> +}
>> +
>> +static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>> + struct drm_framebuffer *old_fb)
>> +{
>> + int ret;
>> +
>> + ret = rockchip_update_plane_helper(crtc->primary, crtc,
>> + crtc->primary->fb, x, y);
>> + if (ret < 0) {
>> + DRM_ERROR("fail to update plane\n");
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void rockchip_drm_crtc_commit(struct drm_crtc *crtc)
>> +{
>> + /* just do dummy now */
>> +}
>> +
>> +static const struct drm_crtc_helper_funcs rockchip_crtc_helper_funcs = {
>> + .dpms = rockchip_drm_crtc_dpms,
>> + .prepare = rockchip_drm_crtc_prepare,
>> + .mode_fixup = rockchip_drm_crtc_mode_fixup,
>> + .mode_set = rockchip_drm_crtc_mode_set,
>> + .mode_set_base = rockchip_drm_crtc_mode_set_base,
>> + .commit = rockchip_drm_crtc_commit,
>> +};
>> +
>> +static int rockchip_drm_crtc_page_flip(struct drm_crtc *crtc,
>> + struct drm_framebuffer *fb,
>> + struct drm_pending_vblank_event *event,
>> + uint32_t page_flip_flags)
>> +{
>> + struct drm_device *dev = crtc->dev;
>> + struct vop_context *ctx = to_vop_ctx(crtc);
>> + struct drm_framebuffer *old_fb = crtc->primary->fb;
>> + int ret;
>> +
>> + /* when the page flip is requested, crtc's dpms should be on */
>> + if (ctx->dpms > DRM_MODE_DPMS_ON) {
>> + DRM_DEBUG("failed page flip request at dpms[%d].\n", ctx->dpms);
>> + return 0;
>> + }
>> +
>> + ret = drm_vblank_get(dev, ctx->pipe);
>> + if (ret) {
>> + DRM_DEBUG("failed to acquire vblank counter\n");
>> + return ret;
>> + }
>> +
>> + spin_lock_irq(&dev->event_lock);
>> + if (ctx->event) {
>> + spin_unlock_irq(&dev->event_lock);
>> + DRM_ERROR("already pending flip!\n");
>> + return -EBUSY;
>> + }
>> + ctx->event = event;
>> + atomic_set(&ctx->wait_vsync_event, 1);
>> + spin_unlock_irq(&dev->event_lock);
>> +
>> + crtc->primary->fb = fb;
>> +
>> + ret = rockchip_update_plane_helper(crtc->primary, crtc, fb,
>> + crtc->x, crtc->y);
>> + if (ret) {
>> + crtc->primary->fb = old_fb;
>> +
>> + spin_lock_irq(&dev->event_lock);
>> + drm_vblank_put(dev, ctx->pipe);
>> + atomic_set(&ctx->wait_vsync_event, 0);
>> + ctx->event = NULL;
>> + spin_unlock_irq(&dev->event_lock);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static void rockchip_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe)
>> +{
>> + struct rockchip_drm_private *dev_priv = dev->dev_private;
>> + struct drm_crtc *drm_crtc = dev_priv->crtc[pipe];
>> + struct vop_context *ctx;
>> + unsigned long flags;
>> +
>> + ctx = to_vop_ctx(drm_crtc);
>> +
>> + spin_lock_irqsave(&dev->event_lock, flags);
>> +
>> + if (ctx->event) {
>> + drm_send_vblank_event(dev, -1, ctx->event);
>> + drm_vblank_put(dev, pipe);
>> + atomic_set(&ctx->wait_vsync_event, 0);
>> + wake_up(&ctx->wait_vsync_queue);
>> + ctx->event = NULL;
>> + }
>> +
>> + spin_unlock_irqrestore(&dev->event_lock, flags);
>> +}
>> +
>> +static void rockchip_drm_crtc_destroy(struct drm_crtc *crtc)
>> +{
>> + struct vop_context *ctx = to_vop_ctx(crtc);
>> + struct rockchip_drm_private *private = crtc->dev->dev_private;
>> +
>> + private->crtc[ctx->pipe] = NULL;
>> + drm_crtc_cleanup(crtc);
>> +}
>> +
>> +static const struct drm_crtc_funcs rockchip_crtc_funcs = {
>> + .set_config = drm_crtc_helper_set_config,
>> + .page_flip = rockchip_drm_crtc_page_flip,
>> + .destroy = rockchip_drm_crtc_destroy,
>> +};
>> +
>> +static void rockchip_vsync_worker(struct work_struct *work)
>> +{
>> + struct vop_context *ctx = container_of(work, struct vop_context,
>> + vsync_work);
>> + struct drm_device *drm = ctx->drm_dev;
>> + struct rockchip_drm_private *dev_priv = drm->dev_private;
>> + struct drm_crtc *crtc = dev_priv->crtc[ctx->pipe];
>> + struct rockchip_plane *rockchip_plane;
>> + struct drm_plane *plane;
>> + uint32_t yrgb_mst;
>> +
>> + mutex_lock(&ctx->vsync_mutex);
>> +
>> + ctx->vsync_work_pending = false;
>> +
>> + list_for_each_entry(plane, &drm->mode_config.plane_list, head) {
>> + rockchip_plane = to_rockchip_plane(plane);
>> +
>> + if (to_vop_ctx(plane->crtc) != ctx)
>> + continue;
>> + if (rockchip_plane->enabled && !rockchip_plane->pending_fb)
>> + continue;
>> + if (!rockchip_plane->enabled && !rockchip_plane->front_fb)
>> + continue;
>> + /*
>> + * make sure the yrgb_mst take effect, so that
>> + * we can unreference the old framebuffer.
>> + */
>> + yrgb_mst = VOP_WIN_GET_YRGBADDR(ctx, rockchip_plane->win);
>> + if (rockchip_plane->pending_yrgb_mst != yrgb_mst) {
>> + /*
>> + * some plane no complete, unref at next vblank
>> + */
>> + ctx->vsync_work_pending = true;
>> + continue;
>> + }
>> +
>> + /*
>> + * drm_framebuffer_unreference maybe call iommu unmap,
>> + * and iommu not allow unmap buffer at irq context,
>> + * so we do drm_framebuffer_unreference at queue_work.
>> + */
>> + if (rockchip_plane->front_fb)
>> + drm_framebuffer_unreference(rockchip_plane->front_fb);
>> +
>> + rockchip_plane->front_fb = rockchip_plane->pending_fb;
>> + rockchip_plane->pending_fb = NULL;
>> +
>> + /*
>> + * if primary plane flip complete, sending the event to
>> + * userspace
>> + */
>> + if (&rockchip_plane->base == crtc->primary)
>> + rockchip_drm_crtc_finish_pageflip(ctx->drm_dev,
>> + ctx->pipe);
>> + }
>> +
>> + mutex_unlock(&ctx->vsync_mutex);
>> +}
>> +
>> +static irqreturn_t rockchip_vop_isr(int irq, void *data)
>> +{
>> + struct vop_context *ctx = data;
>> + uint32_t intr0_reg;
>> + unsigned long flags;
>> +
>> + intr0_reg = vop_readl(ctx, INTR_CTRL0);
>> + if (intr0_reg & LINE_FLAG_INTR) {
>> + spin_lock_irqsave(&ctx->irq_lock, flags);
>> + vop_writel(ctx, INTR_CTRL0, intr0_reg | LINE_FLAG_INTR_CLR);
>> + spin_unlock_irqrestore(&ctx->irq_lock, flags);
>> + } else {
>> + return IRQ_NONE;
>> + }
>> +
>> + drm_handle_vblank(ctx->drm_dev, ctx->pipe);
>> + if (ctx->vsync_work_pending)
>> + queue_work(ctx->vsync_wq, &ctx->vsync_work);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int vop_create_crtc(struct vop_context *ctx)
>> +{
>> + struct device *dev = ctx->dev;
>> + struct drm_device *drm_dev = ctx->drm_dev;
>> + struct drm_plane *primary, *cursor;
>> + unsigned long possible_crtcs;
>> + struct drm_crtc *crtc;
>> + int ret;
>> + int nr;
>> +
>> + ctx->win_mask = 0;
>> + crtc = &ctx->crtc;
>> +
>> + ret = rockchip_drm_add_crtc(drm_dev, crtc, dev->of_node);
>> + if (ret < 0)
>> + return ret;
>> + ctx->pipe = ret;
>> +
>> + possible_crtcs = (1 << ctx->pipe);
>> +
>> + for (nr = 0; nr < VOP_MAX_WIN_SUPPORT; nr++) {
>> + if (nr == VOP_DEFAULT_PRIMARY) {
>> + primary = rockchip_plane_init(ctx, possible_crtcs,
>> + DRM_PLANE_TYPE_PRIMARY,
>> + VOP_DEFAULT_PRIMARY);
>> + if (IS_ERR(primary)) {
>> + DRM_ERROR("fail to init primary plane\n");
>> + return PTR_ERR(primary);
>> + }
>> + } else if (nr == VOP_DEFAULT_CURSOR) {
>> + cursor = rockchip_plane_init(ctx, possible_crtcs,
>> + DRM_PLANE_TYPE_CURSOR,
>> + VOP_DEFAULT_CURSOR);
>> + if (IS_ERR(cursor)) {
>> + DRM_ERROR("fail to init cursor plane\n");
>> + return PTR_ERR(cursor);
>> + }
>> + } else {
>> + struct drm_plane *plane;
>> +
>> + plane = rockchip_plane_init(ctx, possible_crtcs,
>> + DRM_PLANE_TYPE_OVERLAY,
>> + nr);
>> + if (IS_ERR(plane)) {
>> + DRM_ERROR("fail to init overlay plane\n");
>> + return PTR_ERR(plane);
>> + }
>> + }
>> + }
>> +
>> + drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
>> + &rockchip_crtc_funcs);
>> + drm_crtc_helper_add(crtc, &rockchip_crtc_helper_funcs);
>> +
>> + return 0;
>> +}
>> +
>> +static int rockchip_vop_initial(struct vop_context *ctx)
>> +{
>> + struct vop_driver_data *vop_data = ctx->data;
>> + const struct vop_reg_data *init_table = vop_data->init_table;
>> + struct reset_control *ahb_rst;
>> + int i, ret;
>> +
>> + ctx->hclk = devm_clk_get(ctx->dev, "hclk_vop");
>> + if (IS_ERR(ctx->hclk)) {
>> + dev_err(ctx->dev, "failed to get hclk source\n");
>> + return PTR_ERR(ctx->hclk);
>> + }
>> + ctx->aclk = devm_clk_get(ctx->dev, "aclk_vop");
>> + if (IS_ERR(ctx->aclk)) {
>> + dev_err(ctx->dev, "failed to get aclk source\n");
>> + return PTR_ERR(ctx->aclk);
>> + }
>> + ctx->dclk = devm_clk_get(ctx->dev, "dclk_vop");
>> + if (IS_ERR(ctx->dclk)) {
>> + dev_err(ctx->dev, "failed to get dclk source\n");
>> + return PTR_ERR(ctx->dclk);
>> + }
>> +
>> + ret = clk_prepare(ctx->hclk);
>> + if (ret < 0) {
>> + dev_err(ctx->dev, "failed to prepare hclk\n");
>> + return ret;
>> + }
>> +
>> + ret = clk_prepare(ctx->dclk);
>> + if (ret < 0) {
>> + dev_err(ctx->dev, "failed to prepare dclk\n");
>> + goto err_prepare_hclk;
> goto err_unprepare_hclk;
>
>> + }
>> +
>> + ret = clk_prepare(ctx->aclk);
>> + if (ret < 0) {
>> + dev_err(ctx->dev, "failed to prepare aclk\n");
>> + goto err_prepare_dclk;
> goto err_unprepare_dclk;
>
>> + }
>> +
>> + ret = vop_clk_enable(ctx);
>> + if (ret < 0)
>> + goto err_prepare_aclk;
> goto err_unprepare_aclk;
>
>> +
>> + /*
>> + * do hclk_reset, reset all vop registers.
>> + */
>> + ahb_rst = devm_reset_control_get(ctx->dev, "ahb");
>> + if (IS_ERR(ahb_rst)) {
>> + dev_err(ctx->dev, "failed to get ahb reset\n");
>> + ret = PTR_ERR(ahb_rst);
>> + goto err_diable_clk;
>> + }
>> + reset_control_assert(ahb_rst);
>> + usleep_range(10, 20);
>> + reset_control_deassert(ahb_rst);
>> +
>> + memcpy(ctx->regsbak, ctx->regs, ctx->len);
>> +
>> + for (i = 0; i < vop_data->table_size; i++)
>> + vop_writel(ctx, init_table[i].offset, init_table[i].value);
>> +
>> + for (i = 0; i < VOP_MAX_WIN_SUPPORT; i++)
>> + VOP_WIN_SET(ctx, vop_data->win[i], enable, 0);
>> +
>> + vop_cfg_done(ctx);
>> +
>> + /*
>> + * do dclk_reset, let all win config take affect, and then we can enable
>> + * iommu safe.
>> + */
>> + ctx->dclk_rst = devm_reset_control_get(ctx->dev, "dclk");
>> + if (IS_ERR(ctx->dclk_rst)) {
>> + dev_err(ctx->dev, "failed to get dclk reset\n");
>> + ret = PTR_ERR(ctx->dclk_rst);
>> + goto err_diable_clk;
> goto err_disable_clk;
>
>> + }
>> + reset_control_assert(ctx->dclk_rst);
>> + usleep_range(10, 20);
>> + reset_control_deassert(ctx->dclk_rst);
>> +
>> + ctx->dpms = DRM_MODE_DPMS_ON;
> The reset at probe time is probably a good idea.
>
> However, this leaves the VOP enabled and clocking at... but what will it be
> displaying, and what mode will it use?
>
> Perhaps we should delay enabling clocks / dpms until the first mode set /
> dpms(DPMS_ON) request?
OK, let me try it.
>> +
>> + return 0;
>> +
>> +err_prepare_aclk:
>> + clk_unprepare(ctx->aclk);
>> +err_prepare_dclk:
>> + clk_unprepare(ctx->dclk);
>> +err_prepare_hclk:
>> + clk_unprepare(ctx->hclk);
>> +err_diable_clk:
>> + vop_clk_disable(ctx);
>> + return ret;
> err_disable_clk:
> vop_clk_disable(ctx);
> err_unprepare_aclk:
> clk_unprepare(ctx->aclk);
> err_unprepare_dclk:
> clk_unprepare(ctx->dclk);
> err_unprepare_hclk:
> clk_unprepare(ctx->hclk);
> return ret;
>
> That's it for now!
>
> -djk
Thanks for you review:-) .
>
>> +}
>> +
>> +static int vop_bind(struct device *dev, struct device *master, void *data)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct vop_driver_data *vop_data = vop_get_driver_data(dev);
>> + struct drm_device *drm_dev = data;
>> + struct vop_context *ctx;
>> + struct resource *res;
>> + int ret;
>> +
>> + if (!vop_data)
>> + return -ENODEV;
>> +
>> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>> + if (!ctx)
>> + return -ENOMEM;
>> +
>> + ctx->dev = dev;
>> + ctx->data = vop_data;
>> + ctx->drm_dev = drm_dev;
>> + dev_set_drvdata(dev, ctx);
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + ctx->len = resource_size(res);
>> + ctx->regs = devm_ioremap_resource(dev, res);
>> + if (IS_ERR(ctx->regs))
>> + return PTR_ERR(ctx->regs);
>> +
>> + ctx->regsbak = devm_kzalloc(dev, ctx->len, GFP_KERNEL);
>> + if (!ctx->regsbak)
>> + return -ENOMEM;
>> +
>> + ret = rockchip_vop_initial(ctx);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "cannot initial vop dev - err %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ctx->irq = platform_get_irq(pdev, 0);
>> + if (ctx->irq < 0) {
>> + dev_err(dev, "cannot find irq for vop\n");
>> + return ctx->irq;
>> + }
>> +
>> + spin_lock_init(&ctx->reg_lock);
>> + spin_lock_init(&ctx->irq_lock);
>> +
>> + init_waitqueue_head(&ctx->wait_vsync_queue);
>> + atomic_set(&ctx->wait_vsync_event, 0);
>> +
>> + ret = vop_iommu_init(ctx);
>> + if (ret) {
>> + DRM_ERROR("Failed to setup iommu, %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ctx->vsync_wq = create_singlethread_workqueue("vsync");
>> + if (!ctx->vsync_wq) {
>> + dev_err(dev, "failed to create workqueue\n");
>> + return -EINVAL;
>> + }
>> + INIT_WORK(&ctx->vsync_work, rockchip_vsync_worker);
>> +
>> + mutex_init(&ctx->vsync_mutex);
>> + pm_runtime_enable(&pdev->dev);
>> +
>> + ret = devm_request_irq(dev, ctx->irq, rockchip_vop_isr,
>> + IRQF_SHARED, dev_name(dev), ctx);
>> + if (ret) {
>> + dev_err(dev, "cannot requeset irq%d - err %d\n", ctx->irq, ret);
>> + return ret;
>> + }
>> +
>> + return vop_create_crtc(ctx);
>> +}
>> +
>> +static void vop_unbind(struct device *dev, struct device *master,
>> + void *data)
>> +{
>> + struct drm_device *drm_dev = data;
>> + struct vop_context *ctx = dev_get_drvdata(dev);
>> + struct drm_crtc *crtc = &ctx->crtc;
>> +
>> + drm_crtc_cleanup(crtc);
>> + pm_runtime_disable(dev);
>> + rockchip_drm_remove_crtc(drm_dev, ctx->pipe);
>> +
>> + vop_iommu_fini(ctx);
>> +}
>> +
>> +static const struct component_ops vop_component_ops = {
>> + .bind = vop_bind,
>> + .unbind = vop_unbind,
>> +};
>> +
>> +static int vop_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct vop_context *ctx;
>> +
>> + if (!dev->of_node) {
>> + dev_err(dev, "can't find vop devices\n");
>> + return -ENODEV;
>> + }
>> +
>> + platform_set_drvdata(pdev, ctx);
>> +
>> + return component_add(dev, &vop_component_ops);
>> +}
>> +
>> +static int vop_remove(struct platform_device *pdev)
>> +{
>> + component_del(&pdev->dev, &vop_component_ops);
>> +
>> + return 0;
>> +}
>> +
>> +struct platform_driver rockchip_vop_platform_driver = {
>> + .probe = vop_probe,
>> + .remove = vop_remove,
>> + .driver = {
>> + .name = "rockchip-vop",
>> + .owner = THIS_MODULE,
>> + .of_match_table = of_match_ptr(vop_driver_dt_match),
>> + },
>> +};
>> +
>> +module_platform_driver(rockchip_vop_platform_driver);
>> +
>> +MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
>> +MODULE_DESCRIPTION("ROCKCHIP VOP Driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>> new file mode 100644
>> index 0000000..d2a04f0
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>> @@ -0,0 +1,196 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author:Mark Yao <mark.yao@rock-chips.com>
>> + *
>> + * 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.
>> + */
>> +
>> +#ifndef _ROCKCHIP_DRM_VOP_H
>> +#define _ROCKCHIP_DRM_VOP_H
>> +
>> +/* register definition */
>> +#define REG_CFG_DONE 0x0000
>> +#define VERSION_INFO 0x0004
>> +#define SYS_CTRL 0x0008
>> +#define SYS_CTRL1 0x000c
>> +#define DSP_CTRL0 0x0010
>> +#define DSP_CTRL1 0x0014
>> +#define DSP_BG 0x0018
>> +#define MCU_CTRL 0x001c
>> +#define INTR_CTRL0 0x0020
>> +#define INTR_CTRL1 0x0024
>> +#define WIN0_CTRL0 0x0030
>> +#define WIN0_CTRL1 0x0034
>> +#define WIN0_COLOR_KEY 0x0038
>> +#define WIN0_VIR 0x003c
>> +#define WIN0_YRGB_MST 0x0040
>> +#define WIN0_CBR_MST 0x0044
>> +#define WIN0_ACT_INFO 0x0048
>> +#define WIN0_DSP_INFO 0x004c
>> +#define WIN0_DSP_ST 0x0050
>> +#define WIN0_SCL_FACTOR_YRGB 0x0054
>> +#define WIN0_SCL_FACTOR_CBR 0x0058
>> +#define WIN0_SCL_OFFSET 0x005c
>> +#define WIN0_SRC_ALPHA_CTRL 0x0060
>> +#define WIN0_DST_ALPHA_CTRL 0x0064
>> +#define WIN0_FADING_CTRL 0x0068
>> +/* win1 register */
>> +#define WIN1_CTRL0 0x0070
>> +#define WIN1_CTRL1 0x0074
>> +#define WIN1_COLOR_KEY 0x0078
>> +#define WIN1_VIR 0x007c
>> +#define WIN1_YRGB_MST 0x0080
>> +#define WIN1_CBR_MST 0x0084
>> +#define WIN1_ACT_INFO 0x0088
>> +#define WIN1_DSP_INFO 0x008c
>> +#define WIN1_DSP_ST 0x0090
>> +#define WIN1_SCL_FACTOR_YRGB 0x0094
>> +#define WIN1_SCL_FACTOR_CBR 0x0098
>> +#define WIN1_SCL_OFFSET 0x009c
>> +#define WIN1_SRC_ALPHA_CTRL 0x00a0
>> +#define WIN1_DST_ALPHA_CTRL 0x00a4
>> +#define WIN1_FADING_CTRL 0x00a8
>> +/* win2 register */
>> +#define WIN2_CTRL0 0x00b0
>> +#define WIN2_CTRL1 0x00b4
>> +#define WIN2_VIR0_1 0x00b8
>> +#define WIN2_VIR2_3 0x00bc
>> +#define WIN2_MST0 0x00c0
>> +#define WIN2_DSP_INFO0 0x00c4
>> +#define WIN2_DSP_ST0 0x00c8
>> +#define WIN2_COLOR_KEY 0x00cc
>> +#define WIN2_MST1 0x00d0
>> +#define WIN2_DSP_INFO1 0x00d4
>> +#define WIN2_DSP_ST1 0x00d8
>> +#define WIN2_SRC_ALPHA_CTRL 0x00dc
>> +#define WIN2_MST2 0x00e0
>> +#define WIN2_DSP_INFO2 0x00e4
>> +#define WIN2_DSP_ST2 0x00e8
>> +#define WIN2_DST_ALPHA_CTRL 0x00ec
>> +#define WIN2_MST3 0x00f0
>> +#define WIN2_DSP_INFO3 0x00f4
>> +#define WIN2_DSP_ST3 0x00f8
>> +#define WIN2_FADING_CTRL 0x00fc
>> +/* win3 register */
>> +#define WIN3_CTRL0 0x0100
>> +#define WIN3_CTRL1 0x0104
>> +#define WIN3_VIR0_1 0x0108
>> +#define WIN3_VIR2_3 0x010c
>> +#define WIN3_MST0 0x0110
>> +#define WIN3_DSP_INFO0 0x0114
>> +#define WIN3_DSP_ST0 0x0118
>> +#define WIN3_COLOR_KEY 0x011c
>> +#define WIN3_MST1 0x0120
>> +#define WIN3_DSP_INFO1 0x0124
>> +#define WIN3_DSP_ST1 0x0128
>> +#define WIN3_SRC_ALPHA_CTRL 0x012c
>> +#define WIN3_MST2 0x0130
>> +#define WIN3_DSP_INFO2 0x0134
>> +#define WIN3_DSP_ST2 0x0138
>> +#define WIN3_DST_ALPHA_CTRL 0x013c
>> +#define WIN3_MST3 0x0140
>> +#define WIN3_DSP_INFO3 0x0144
>> +#define WIN3_DSP_ST3 0x0148
>> +#define WIN3_FADING_CTRL 0x014c
>> +/* hwc register */
>> +#define HWC_CTRL0 0x0150
>> +#define HWC_CTRL1 0x0154
>> +#define HWC_MST 0x0158
>> +#define HWC_DSP_ST 0x015c
>> +#define HWC_SRC_ALPHA_CTRL 0x0160
>> +#define HWC_DST_ALPHA_CTRL 0x0164
>> +#define HWC_FADING_CTRL 0x0168
>> +/* post process register */
>> +#define POST_DSP_HACT_INFO 0x0170
>> +#define POST_DSP_VACT_INFO 0x0174
>> +#define POST_SCL_FACTOR_YRGB 0x0178
>> +#define POST_SCL_CTRL 0x0180
>> +#define POST_DSP_VACT_INFO_F1 0x0184
>> +#define DSP_HTOTAL_HS_END 0x0188
>> +#define DSP_HACT_ST_END 0x018c
>> +#define DSP_VTOTAL_VS_END 0x0190
>> +#define DSP_VACT_ST_END 0x0194
>> +#define DSP_VS_ST_END_F1 0x0198
>> +#define DSP_VACT_ST_END_F1 0x019c
>> +/* register definition end */
>> +
>> +/* interrupt define */
>> +#define DSP_HOLD_VALID_INTR (1 << 0)
>> +#define FS_INTR (1 << 1)
>> +#define LINE_FLAG_INTR (1 << 2)
>> +#define BUS_ERROR_INTR (1 << 3)
>> +
>> +#define DSP_HOLD_VALID_INTR_EN(x) ((x) << 4)
>> +#define FS_INTR_EN(x) ((x) << 5)
>> +#define LINE_FLAG_INTR_EN(x) ((x) << 6)
>> +#define BUS_ERROR_INTR_EN(x) ((x) << 7)
>> +#define DSP_HOLD_VALID_INTR_MASK (1 << 4)
>> +#define FS_INTR_EN_MASK (1 << 5)
>> +#define LINE_FLAG_INTR_MASK (1 << 6)
>> +#define BUS_ERROR_INTR_MASK (1 << 7)
>> +
>> +#define DSP_HOLD_VALID_INTR_CLR (1 << 8)
>> +#define FS_INTR_EN_CLR (1 << 9)
>> +#define LINE_FLAG_INTR_CLR (1 << 10)
>> +#define BUS_ERROR_INTR_CLR (1 << 11)
>> +#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
>> +#define DSP_LINE_NUM_MASK (0x1fff << 12)
>> +
>> +/* src alpha ctrl define */
>> +#define SRC_FADING_VALUE(x) (((x) & 0xff) << 24)
>> +#define SRC_GLOBAL_ALPHA(x) (((x) & 0xff) << 16)
>> +#define SRC_FACTOR_M0(x) (((x) & 0x7) << 6)
>> +#define SRC_ALPHA_CAL_M0(x) (((x) & 0x1) << 5)
>> +#define SRC_BLEND_M0(x) (((x) & 0x3) << 3)
>> +#define SRC_ALPHA_M0(x) (((x) & 0x1) << 2)
>> +#define SRC_COLOR_M0(x) (((x) & 0x1) << 1)
>> +#define SRC_ALPHA_EN(x) (((x) & 0x1) << 0)
>> +/* dst alpha ctrl define */
>> +#define DST_FACTOR_M0(x) (((x) & 0x7) << 6)
>> +
>> +/*
>> + * display output interface supported by rockchip lcdc
>> + */
>> +#define ROCKCHIP_OUT_MODE_P888 0
>> +#define ROCKCHIP_OUT_MODE_P666 1
>> +#define ROCKCHIP_OUT_MODE_P565 2
>> +/* for use special outface */
>> +#define ROCKCHIP_OUT_MODE_AAAA 15
>> +
>> +enum alpha_mode {
>> + ALPHA_STRAIGHT,
>> + ALPHA_INVERSE,
>> +};
>> +
>> +enum global_blend_mode {
>> + ALPHA_GLOBAL,
>> + ALPHA_PER_PIX,
>> + ALPHA_PER_PIX_GLOBAL,
>> +};
>> +
>> +enum alpha_cal_mode {
>> + ALPHA_SATURATION,
>> + ALPHA_NO_SATURATION,
>> +};
>> +
>> +enum color_mode {
>> + ALPHA_SRC_PRE_MUL,
>> + ALPHA_SRC_NO_PRE_MUL,
>> +};
>> +
>> +enum factor_mode {
>> + ALPHA_ZERO,
>> + ALPHA_ONE,
>> + ALPHA_SRC,
>> + ALPHA_SRC_INVERSE,
>> + ALPHA_SRC_GLOBAL,
>> +};
>> +
>> +#endif /* _ROCKCHIP_DRM_VOP_H */
>> --
>> 1.7.9.5
>>
>>
>
>
[-- Attachment #1.2: Type: text/html, Size: 130663 bytes --]
[-- Attachment #2: Type: text/plain, Size: 159 bytes --]
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2014-09-30 0:35 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-09-26 10:53 [RESENT PATCH v7 0/3] Add drm driver for Rockchip Socs Mark Yao
[not found] ` <1411728800-25112-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
2014-09-26 10:55 ` [RESENT PATCH v7 1/3] drm: rockchip: Add basic drm driver Mark Yao
[not found] ` <1411728904-25182-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
2014-09-29 4:01 ` Daniel Kurtz
2014-09-30 0:35 ` Mark yao
2014-09-26 10:57 ` [RESENT PATCH v7 2/3] dt-bindings: video: Add for rockchip display subsytem Mark Yao
2014-09-26 10:58 ` [RESENT PATCH v7 3/3] dt-bindings: video: Add documentation for rockchip vop Mark Yao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).