2019-12-10 08:37:30

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 0/8] Add Unisoc's drm kms module

Hi all,
This patch is our Unisoc's new drm display driver, This driver
provides support for the Direct Rendering Infrastructure (DRI)
in XFree86 4.1.0 and higher.

This patch include display controller, mipi dsi and mipi dphy support
for Unisoc's display subsystem.

Best,
Kevin Tang

Kevin Tang (8):
dt-bindings: display: add Unisoc's drm master bindings
drm/sprd: add Unisoc's drm kms master
dt-bindings: display: add Unisoc's dpu bindings
drm/sprd: add Unisoc's drm display controller driver
dt-bindings: display: add Unisoc's mipi dsi&dphy bindings
drm/sprd: add Unisoc's drm mipi dsi&dphy driver
dt-bindings: display: add Unisoc's generic mipi panel bindings
drm/sprd: add Unisoc's drm generic mipi panel driver

.../devicetree/bindings/display/sprd/dphy.txt | 49 +
.../devicetree/bindings/display/sprd/dpu.txt | 55 +
.../devicetree/bindings/display/sprd/drm.txt | 18 +
.../devicetree/bindings/display/sprd/dsi.txt | 68 +
.../devicetree/bindings/display/sprd/panel.txt | 110 ++
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/sprd/Kconfig | 14 +
drivers/gpu/drm/sprd/Makefile | 17 +
drivers/gpu/drm/sprd/disp_lib.c | 290 ++++
drivers/gpu/drm/sprd/disp_lib.h | 40 +
drivers/gpu/drm/sprd/dphy/Makefile | 7 +
drivers/gpu/drm/sprd/dphy/pll/Makefile | 3 +
drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c | 640 +++++++++
drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c | 254 ++++
drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h | 329 +++++
drivers/gpu/drm/sprd/dpu/Makefile | 8 +
drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 1464 ++++++++++++++++++++
drivers/gpu/drm/sprd/dsi/Makefile | 7 +
drivers/gpu/drm/sprd/dsi/core/Makefile | 3 +
drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c | 1186 ++++++++++++++++
drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h | 1417 +++++++++++++++++++
drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c | 392 ++++++
drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c | 544 ++++++++
drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h | 28 +
drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h | 1102 +++++++++++++++
drivers/gpu/drm/sprd/sprd_dphy.c | 235 ++++
drivers/gpu/drm/sprd/sprd_dphy.h | 121 ++
drivers/gpu/drm/sprd/sprd_dpu.c | 1152 +++++++++++++++
drivers/gpu/drm/sprd/sprd_dpu.h | 217 +++
drivers/gpu/drm/sprd/sprd_drm.c | 287 ++++
drivers/gpu/drm/sprd/sprd_drm.h | 19 +
drivers/gpu/drm/sprd/sprd_dsi.c | 722 ++++++++++
drivers/gpu/drm/sprd/sprd_dsi.h | 210 +++
drivers/gpu/drm/sprd/sprd_gem.c | 178 +++
drivers/gpu/drm/sprd/sprd_gem.h | 30 +
drivers/gpu/drm/sprd/sprd_panel.c | 778 +++++++++++
drivers/gpu/drm/sprd/sprd_panel.h | 114 ++
38 files changed, 12111 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sprd/dphy.txt
create mode 100644 Documentation/devicetree/bindings/display/sprd/dpu.txt
create mode 100644 Documentation/devicetree/bindings/display/sprd/drm.txt
create mode 100644 Documentation/devicetree/bindings/display/sprd/dsi.txt
create mode 100644 Documentation/devicetree/bindings/display/sprd/panel.txt
create mode 100644 drivers/gpu/drm/sprd/Kconfig
create mode 100644 drivers/gpu/drm/sprd/Makefile
create mode 100644 drivers/gpu/drm/sprd/disp_lib.c
create mode 100644 drivers/gpu/drm/sprd/disp_lib.h
create mode 100644 drivers/gpu/drm/sprd/dphy/Makefile
create mode 100644 drivers/gpu/drm/sprd/dphy/pll/Makefile
create mode 100644 drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c
create mode 100644 drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c
create mode 100644 drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h
create mode 100644 drivers/gpu/drm/sprd/dpu/Makefile
create mode 100644 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
create mode 100644 drivers/gpu/drm/sprd/dsi/Makefile
create mode 100644 drivers/gpu/drm/sprd/dsi/core/Makefile
create mode 100644 drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c
create mode 100644 drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h
create mode 100644 drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c
create mode 100644 drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c
create mode 100644 drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h
create mode 100644 drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h
create mode 100644 drivers/gpu/drm/sprd/sprd_dphy.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dphy.h
create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.h
create mode 100644 drivers/gpu/drm/sprd/sprd_drm.c
create mode 100644 drivers/gpu/drm/sprd/sprd_drm.h
create mode 100644 drivers/gpu/drm/sprd/sprd_dsi.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dsi.h
create mode 100644 drivers/gpu/drm/sprd/sprd_gem.c
create mode 100644 drivers/gpu/drm/sprd/sprd_gem.h
create mode 100644 drivers/gpu/drm/sprd/sprd_panel.c
create mode 100644 drivers/gpu/drm/sprd/sprd_panel.h

--
2.7.4


2019-12-10 08:37:35

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 1/8] dt-bindings: display: add Unisoc's drm master bindings

From: Kevin Tang <[email protected]>

The Unisoc DRM master device is a virtual device needed to list all
DPU devices or other display interface nodes that comprise the
graphics subsystem

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
Documentation/devicetree/bindings/display/sprd/drm.txt | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sprd/drm.txt

diff --git a/Documentation/devicetree/bindings/display/sprd/drm.txt b/Documentation/devicetree/bindings/display/sprd/drm.txt
new file mode 100644
index 0000000..7327b9e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sprd/drm.txt
@@ -0,0 +1,18 @@
+Unisoc DRM master device
+================================
+
+The Unisoc DRM master device is a virtual device needed to list all
+DPU devices or other display interface nodes that comprise the
+graphics subsystem.
+
+Required properties:
+- compatible: Should be "sprd,display-subsystem"
+- ports: Should contain a list of phandles pointing to display interface port
+ of DPU devices.
+
+example:
+
+display-subsystem {
+ compatible = "sprd,display-subsystem";
+ ports = <&dpu_out>;
+};
--
2.7.4

2019-12-10 08:37:43

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master

From: Kevin Tang <[email protected]>

Adds drm support for the Unisoc's display subsystem.

This is drm device and gem driver. This driver provides support for the
Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/sprd/Kconfig | 14 ++
drivers/gpu/drm/sprd/Makefile | 8 ++
drivers/gpu/drm/sprd/sprd_drm.c | 287 ++++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/sprd/sprd_drm.h | 19 +++
drivers/gpu/drm/sprd/sprd_gem.c | 178 +++++++++++++++++++++++++
drivers/gpu/drm/sprd/sprd_gem.h | 30 +++++
8 files changed, 539 insertions(+)
create mode 100644 drivers/gpu/drm/sprd/Kconfig
create mode 100644 drivers/gpu/drm/sprd/Makefile
create mode 100644 drivers/gpu/drm/sprd/sprd_drm.c
create mode 100644 drivers/gpu/drm/sprd/sprd_drm.h
create mode 100644 drivers/gpu/drm/sprd/sprd_gem.c
create mode 100644 drivers/gpu/drm/sprd/sprd_gem.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index bfdadc3..cead12c 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -387,6 +387,8 @@ source "drivers/gpu/drm/aspeed/Kconfig"

source "drivers/gpu/drm/mcde/Kconfig"

+source "drivers/gpu/drm/sprd/Kconfig"
+
# Keep legacy drivers last

menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 9f1c7c4..85ca211 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -122,3 +122,4 @@ obj-$(CONFIG_DRM_LIMA) += lima/
obj-$(CONFIG_DRM_PANFROST) += panfrost/
obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
obj-$(CONFIG_DRM_MCDE) += mcde/
+obj-$(CONFIG_DRM_SPRD) += sprd/
diff --git a/drivers/gpu/drm/sprd/Kconfig b/drivers/gpu/drm/sprd/Kconfig
new file mode 100644
index 0000000..79f286b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/Kconfig
@@ -0,0 +1,14 @@
+config DRM_SPRD
+ tristate "DRM Support for Unisoc SoCs Platform"
+ depends on ARCH_SPRD
+ depends on DRM && OF
+ select DRM_KMS_HELPER
+ select DRM_GEM_CMA_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_MIPI_DSI
+ select DRM_PANEL
+ select VIDEOMODE_HELPERS
+ select BACKLIGHT_CLASS_DEVICE
+ help
+ Choose this option if you have a Unisoc chipsets.
+ If M is selected the module will be called sprd-drm.
\ No newline at end of file
diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
new file mode 100644
index 0000000..df0b316
--- /dev/null
+++ b/drivers/gpu/drm/sprd/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+ccflags-y += -Iinclude/drm
+
+subdir-ccflags-y += -I$(src)
+
+obj-y := sprd_drm.o \
+ sprd_gem.o
\ No newline at end of file
diff --git a/drivers/gpu/drm/sprd/sprd_drm.c b/drivers/gpu/drm/sprd/sprd_drm.c
new file mode 100644
index 0000000..ec16fee
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_drm.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/component.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "sprd_drm.h"
+#include "sprd_gem.h"
+
+#define DRIVER_NAME "sprd"
+#define DRIVER_DESC "Spreadtrum SoCs' DRM Driver"
+#define DRIVER_DATE "20180501"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+
+static const struct drm_mode_config_helper_funcs sprd_drm_mode_config_helper = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+static const struct drm_mode_config_funcs sprd_drm_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static void sprd_drm_mode_config_init(struct drm_device *drm)
+{
+ drm_mode_config_init(drm);
+
+ drm->mode_config.min_width = 0;
+ drm->mode_config.min_height = 0;
+ drm->mode_config.max_width = 8192;
+ drm->mode_config.max_height = 8192;
+ drm->mode_config.allow_fb_modifiers = true;
+
+ drm->mode_config.funcs = &sprd_drm_mode_config_funcs;
+ drm->mode_config.helper_private = &sprd_drm_mode_config_helper;
+}
+
+static const struct file_operations sprd_drm_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .compat_ioctl = drm_compat_ioctl,
+ .poll = drm_poll,
+ .read = drm_read,
+ .llseek = no_llseek,
+ .mmap = sprd_gem_cma_mmap,
+};
+
+static struct drm_driver sprd_drm_drv = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET |
+ DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
+ .fops = &sprd_drm_fops,
+
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .gem_free_object_unlocked = sprd_gem_free_object,
+ .dumb_create = sprd_gem_cma_dumb_create,
+
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_import_sg_table = sprd_gem_prime_import_sg_table,
+
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+};
+
+static int sprd_drm_bind(struct device *dev)
+{
+ struct drm_device *drm;
+ struct sprd_drm *sprd;
+ int err;
+
+ drm = drm_dev_alloc(&sprd_drm_drv, dev);
+ if (IS_ERR(drm))
+ return PTR_ERR(drm);
+
+ dev_set_drvdata(dev, drm);
+
+ sprd = devm_kzalloc(drm->dev, sizeof(*sprd), GFP_KERNEL);
+ if (!sprd) {
+ err = -ENOMEM;
+ goto err_free_drm;
+ }
+ drm->dev_private = sprd;
+
+ sprd_drm_mode_config_init(drm);
+
+ /* bind and init sub drivers */
+ err = component_bind_all(drm->dev, drm);
+ if (err) {
+ DRM_ERROR("failed to bind all component.\n");
+ goto err_dc_cleanup;
+ }
+
+ /* vblank init */
+ err = drm_vblank_init(drm, drm->mode_config.num_crtc);
+ if (err) {
+ DRM_ERROR("failed to initialize vblank.\n");
+ goto err_unbind_all;
+ }
+ /* with irq_enabled = true, we can use the vblank feature. */
+ drm->irq_enabled = true;
+
+ /* reset all the states of crtc/plane/encoder/connector */
+ drm_mode_config_reset(drm);
+
+ /* init kms poll for handling hpd */
+ drm_kms_helper_poll_init(drm);
+
+ err = drm_dev_register(drm, 0);
+ if (err < 0)
+ goto err_kms_helper_poll_fini;
+
+ return 0;
+
+err_kms_helper_poll_fini:
+ drm_kms_helper_poll_fini(drm);
+err_unbind_all:
+ component_unbind_all(drm->dev, drm);
+err_dc_cleanup:
+ drm_mode_config_cleanup(drm);
+err_free_drm:
+ drm_dev_put(drm);
+ return err;
+}
+
+static void sprd_drm_unbind(struct device *dev)
+{
+ drm_put_dev(dev_get_drvdata(dev));
+}
+
+static const struct component_master_ops sprd_drm_component_ops = {
+ .bind = sprd_drm_bind,
+ .unbind = sprd_drm_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+ struct device_node *np = data;
+
+ DRM_DEBUG("compare %s\n", np->full_name);
+
+ return dev->of_node == np;
+}
+
+static int sprd_drm_component_probe(struct device *dev,
+ const struct component_master_ops *m_ops)
+{
+ struct device_node *ep, *port, *remote;
+ struct component_match *match = NULL;
+ int i;
+
+ if (!dev->of_node)
+ return -EINVAL;
+
+ /*
+ * Bind the crtc's ports first, so that drm_of_find_possible_crtcs()
+ * called from encoder's .bind callbacks works as expected
+ */
+ for (i = 0; ; i++) {
+ port = of_parse_phandle(dev->of_node, "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;
+ }
+
+ if (!match) {
+ dev_err(dev, "no available port\n");
+ return -ENODEV;
+ }
+
+ /*
+ * For bound crtcs, bind the encoders attached to their remote endpoint
+ */
+ for (i = 0; ; i++) {
+ port = of_parse_phandle(dev->of_node, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ 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);
+ }
+ of_node_put(port);
+ }
+
+ return component_master_add_with_match(dev, m_ops, match);
+}
+
+static int sprd_drm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, ~0);
+ if (ret)
+ DRM_ERROR("dma_set_mask_and_coherent failed (%d)\n", ret);
+
+ return sprd_drm_component_probe(&pdev->dev, &sprd_drm_component_ops);
+}
+
+static int sprd_drm_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &sprd_drm_component_ops);
+ return 0;
+}
+
+static void sprd_drm_shutdown(struct platform_device *pdev)
+{
+ struct drm_device *drm = platform_get_drvdata(pdev);
+
+ if (!drm) {
+ DRM_WARN("drm device is not available, no shutdown\n");
+ return;
+ }
+
+ drm_atomic_helper_shutdown(drm);
+}
+
+static const struct of_device_id sprd_drm_match_table[] = {
+ { .compatible = "sprd,display-subsystem",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, sprd_drm_match_table);
+
+static struct platform_driver sprd_drm_driver = {
+ .probe = sprd_drm_probe,
+ .remove = sprd_drm_remove,
+ .shutdown = sprd_drm_shutdown,
+ .driver = {
+ .name = "sprd-drm-drv",
+ .of_match_table = sprd_drm_match_table,
+ },
+};
+
+module_platform_driver(sprd_drm_driver);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("Unisoc DRM KMS Master Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_drm.h b/drivers/gpu/drm/sprd/sprd_drm.h
new file mode 100644
index 0000000..e840e65
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_drm.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _SPRD_DRM_H_
+#define _SPRD_DRM_H_
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_print.h>
+
+struct sprd_drm {
+ struct drm_device *drm;
+ struct drm_atomic_state *state;
+ struct device *dpu_dev;
+ struct device *gsp_dev;
+};
+
+#endif /* _SPRD_DRM_H_ */
diff --git a/drivers/gpu/drm/sprd/sprd_gem.c b/drivers/gpu/drm/sprd/sprd_gem.c
new file mode 100644
index 0000000..c617c8b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_gem.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_prime.h>
+
+#include "sprd_drm.h"
+#include "sprd_gem.h"
+
+static struct sprd_gem_obj *sprd_gem_obj_create(struct drm_device *drm,
+ unsigned long size)
+{
+ struct sprd_gem_obj *sprd_gem;
+ int ret;
+
+ sprd_gem = kzalloc(sizeof(*sprd_gem), GFP_KERNEL);
+ if (!sprd_gem)
+ return ERR_PTR(-ENOMEM);
+
+ ret = drm_gem_object_init(drm, &sprd_gem->base, size);
+ if (ret < 0) {
+ DRM_ERROR("failed to initialize gem object\n");
+ goto error;
+ }
+
+ ret = drm_gem_create_mmap_offset(&sprd_gem->base);
+ if (ret) {
+ drm_gem_object_release(&sprd_gem->base);
+ goto error;
+ }
+
+ return sprd_gem;
+
+error:
+ kfree(sprd_gem);
+ return ERR_PTR(ret);
+}
+
+void sprd_gem_free_object(struct drm_gem_object *obj)
+{
+ struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
+
+ DRM_DEBUG("gem = %p\n", obj);
+
+ if (sprd_gem->vaddr)
+ dma_alloc_wc(obj->dev->dev, obj->size,
+ sprd_gem->vaddr, sprd_gem->dma_addr);
+ else if (sprd_gem->sgtb)
+ drm_prime_gem_destroy(obj, sprd_gem->sgtb);
+
+ drm_gem_object_release(obj);
+
+ kfree(sprd_gem);
+}
+
+int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm,
+ struct drm_mode_create_dumb *args)
+{
+ struct sprd_gem_obj *sprd_gem;
+ int ret;
+
+ args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+ args->size = round_up(args->pitch * args->height, PAGE_SIZE);
+
+ sprd_gem = sprd_gem_obj_create(drm, args->size);
+ if (IS_ERR(sprd_gem))
+ return PTR_ERR(sprd_gem);
+
+ sprd_gem->vaddr = dma_alloc_wc(drm->dev, args->size,
+ &sprd_gem->dma_addr, GFP_KERNEL | __GFP_NOWARN | GFP_DMA);
+ if (!sprd_gem->vaddr) {
+ DRM_ERROR("failed to allocate buffer with size %llu\n",
+ args->size);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = drm_gem_handle_create(file_priv, &sprd_gem->base, &args->handle);
+ if (ret)
+ goto error;
+
+ drm_gem_object_put_unlocked(&sprd_gem->base);
+
+ return 0;
+
+error:
+ sprd_gem_free_object(&sprd_gem->base);
+ return ret;
+}
+
+static int sprd_gem_cma_object_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+
+{
+ int ret;
+ struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
+
+ vma->vm_flags &= ~VM_PFNMAP;
+ vma->vm_pgoff = 0;
+
+ ret = dma_mmap_wc(obj->dev->dev, vma,
+ sprd_gem->vaddr, sprd_gem->dma_addr,
+ vma->vm_end - vma->vm_start);
+ if (ret)
+ drm_gem_vm_close(vma);
+
+ return ret;
+}
+
+int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj;
+ int ret;
+
+ ret = drm_gem_mmap(filp, vma);
+ if (ret)
+ return ret;
+
+ obj = vma->vm_private_data;
+
+ return sprd_gem_cma_object_mmap(obj, vma);
+}
+
+int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ int ret;
+
+ ret = drm_gem_mmap_obj(obj, obj->size, vma);
+ if (ret)
+ return ret;
+
+ return sprd_gem_cma_object_mmap(obj, vma);
+}
+
+struct sg_table *sprd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
+ struct sg_table *sgtb;
+ int ret;
+
+ sgtb = kzalloc(sizeof(*sgtb), GFP_KERNEL);
+ if (!sgtb)
+ return ERR_PTR(-ENOMEM);
+
+ ret = dma_get_sgtable(obj->dev->dev, sgtb, sprd_gem->vaddr,
+ sprd_gem->dma_addr, obj->size);
+ if (ret) {
+ DRM_ERROR("failed to allocate sg_table, %d\n", ret);
+ kfree(sgtb);
+ return ERR_PTR(ret);
+ }
+
+ return sgtb;
+}
+
+struct drm_gem_object *sprd_gem_prime_import_sg_table(struct drm_device *drm,
+ struct dma_buf_attachment *attach, struct sg_table *sgtb)
+{
+ struct sprd_gem_obj *sprd_gem;
+
+ sprd_gem = sprd_gem_obj_create(drm, attach->dmabuf->size);
+ if (IS_ERR(sprd_gem))
+ return ERR_CAST(sprd_gem);
+
+ DRM_DEBUG("gem = %p\n", &sprd_gem->base);
+
+ if (sgtb->nents == 1)
+ sprd_gem->dma_addr = sg_dma_address(sgtb->sgl);
+
+ sprd_gem->sgtb = sgtb;
+
+ return &sprd_gem->base;
+}
diff --git a/drivers/gpu/drm/sprd/sprd_gem.h b/drivers/gpu/drm/sprd/sprd_gem.h
new file mode 100644
index 0000000..4c10d8a
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_gem.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _SPRD_GEM_H_
+#define _SPRD_GEM_H_
+
+#include <drm/drm_gem.h>
+
+struct sprd_gem_obj {
+ struct drm_gem_object base;
+ dma_addr_t dma_addr;
+ struct sg_table *sgtb;
+ void *vaddr;
+};
+
+#define to_sprd_gem_obj(x) container_of(x, struct sprd_gem_obj, base)
+
+void sprd_gem_free_object(struct drm_gem_object *gem);
+int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma);
+int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma);
+struct sg_table *sprd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj);
+struct drm_gem_object *sprd_gem_prime_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach, struct sg_table *sgtb);
+
+#endif
--
2.7.4

2019-12-10 08:37:51

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 3/8] dt-bindings: display: add Unisoc's dpu bindings

From: Kevin Tang <[email protected]>

DPU (Display Processor Unit) is the Display Controller for the Unisoc SoCs
which transfers the image data from a video memory buffer to an internal
LCD interface.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
.../devicetree/bindings/display/sprd/dpu.txt | 55 ++++++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sprd/dpu.txt

diff --git a/Documentation/devicetree/bindings/display/sprd/dpu.txt b/Documentation/devicetree/bindings/display/sprd/dpu.txt
new file mode 100644
index 0000000..25cbf8e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sprd/dpu.txt
@@ -0,0 +1,55 @@
+Unisoc SoC Display Processor Unit (DPU)
+============================================================================
+
+DPU (Display Processor Unit) is the Display Controller for the Unisoc SoCs
+which transfers the image data from a video memory buffer to an internal
+LCD interface.
+
+Required properties:
+ - compatible: value should be "sprd,display-processor";
+ - reg: physical base address and length of the DPU registers set.
+ - interrupts: the interrupt signal from DPU.
+ - clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+ - clock-names: list of clock names sorted in the same order as the clocks
+ property.
+ - dma-coherent: with this property, the dpu driver can allocate large and
+ continuous memorys.
+ - port: a port node with endpoint definitions as defined in document [1].
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Optional Properties:
+ - iommus: a phandle to DPU iommu node.
+ - power-domains: a phandle to DPU power domain node.
+
+
+Example:
+
+SoC specific DT entry:
+
+ dpu: dpu@63000000 {
+ compatible = "sprd,display-processor";
+ reg = <0x0 0x63000000 0x0 0x1000>;
+ interrupts = <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "clk_src_128m",
+ "clk_src_153m6",
+ "clk_src_384m",
+ "clk_dpu_core",
+ "clk_dpu_dpi",
+ "clk_aon_apb_disp_eb";
+
+ clocks = <&clk_twpll_128m>,
+ <&clk_twpll_153m6>,
+ <&clk_twpll_384m>,
+ <&clk_dpu>,
+ <&clk_dpu_dpi>,
+ <&clk_aon_top_gates 2>;
+
+ dma-coherent;
+ dpu_port: port {
+ dpu_out: endpoint {
+ remote-endpoint = <&dsi_in>;
+ };
+ };
+ };
\ No newline at end of file
--
2.7.4

2019-12-10 08:38:00

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 4/8] drm/sprd: add Unisoc's drm display controller driver

From: Kevin Tang <[email protected]>

Adds DPU(Display Processor Unit) support for the Unisoc's display subsystem.
It's support multi planes, scaler, rotation, PQ(Picture Quality) and more.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
drivers/gpu/drm/sprd/Makefile | 6 +-
drivers/gpu/drm/sprd/disp_lib.c | 290 +++++++
drivers/gpu/drm/sprd/disp_lib.h | 40 +
drivers/gpu/drm/sprd/dpu/Makefile | 8 +
drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 1464 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/sprd/sprd_dpu.c | 1152 +++++++++++++++++++++++++++
drivers/gpu/drm/sprd/sprd_dpu.h | 217 ++++++
7 files changed, 3176 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/sprd/disp_lib.c
create mode 100644 drivers/gpu/drm/sprd/disp_lib.h
create mode 100644 drivers/gpu/drm/sprd/dpu/Makefile
create mode 100644 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.h

diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
index df0b316..3f188ab 100644
--- a/drivers/gpu/drm/sprd/Makefile
+++ b/drivers/gpu/drm/sprd/Makefile
@@ -5,4 +5,8 @@ ccflags-y += -Iinclude/drm
subdir-ccflags-y += -I$(src)

obj-y := sprd_drm.o \
- sprd_gem.o
\ No newline at end of file
+ sprd_gem.o \
+ sprd_dpu.o
+
+obj-y += disp_lib.o
+obj-y += dpu/
\ No newline at end of file
diff --git a/drivers/gpu/drm/sprd/disp_lib.c b/drivers/gpu/drm/sprd/disp_lib.c
new file mode 100644
index 0000000..cadd1ad
--- /dev/null
+++ b/drivers/gpu/drm/sprd/disp_lib.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#define pr_fmt(__fmt) "[drm][%20s] "__fmt, __func__
+
+#include <linux/device.h>
+#include <linux/libfdt.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+
+#include "disp_lib.h"
+
+struct bmp_header {
+ u16 magic;
+ u32 size;
+ u32 unused;
+ u32 start;
+} __attribute__((__packed__));
+
+struct dib_header {
+ u32 size;
+ u32 width;
+ u32 height;
+ u16 planes;
+ u16 bpp;
+ u32 compression;
+ u32 data_size;
+ u32 h_res;
+ u32 v_res;
+ u32 colours;
+ u32 important_colours;
+ u32 red_mask;
+ u32 green_mask;
+ u32 blue_mask;
+ u32 alpha_mask;
+ u32 colour_space;
+ u32 unused[12];
+} __attribute__((__packed__));
+
+int str_to_u32_array(const char *p, u32 base, u32 array[])
+{
+ const char *start = p;
+ char str[12];
+ int length = 0;
+ int i, ret;
+
+ pr_info("input: %s", p);
+
+ for (i = 0 ; i < 255; i++) {
+ while (*p == ' ')
+ p++;
+ if (*p == '\0')
+ break;
+ start = p;
+
+ while ((*p != ' ') && (*p != '\0'))
+ p++;
+
+ if ((p - start) >= sizeof(str))
+ break;
+
+ memset(str, 0, sizeof(str));
+ memcpy(str, start, p - start);
+
+ ret = kstrtou32(str, base, &array[i]);
+ if (ret) {
+ DRM_ERROR("input format error\n");
+ break;
+ }
+
+ length++;
+ }
+
+ return length;
+}
+EXPORT_SYMBOL_GPL(str_to_u32_array);
+
+int load_dtb_to_mem(const char *name, void **blob)
+{
+ ssize_t ret;
+ u32 count;
+ struct fdt_header dtbhead;
+ loff_t pos = 0;
+ struct file *fdtb;
+
+
+ fdtb = filp_open(name, O_RDONLY, 0644);
+ if (IS_ERR(fdtb)) {
+ DRM_ERROR("%s open file error\n", __func__);
+ return PTR_ERR(fdtb);
+ }
+
+ ret = kernel_read(fdtb, &dtbhead, sizeof(dtbhead), &pos);
+ pos = 0;
+ count = ntohl(dtbhead.totalsize);
+ *blob = kzalloc(count, GFP_KERNEL);
+ if (*blob == NULL) {
+ filp_close(fdtb, NULL);
+ return -ENOMEM;
+ }
+ ret = kernel_read(fdtb, *blob, count, &pos);
+
+ if (ret != count) {
+ DRM_ERROR("Read to mem fail: ret %zd size%x\n", ret, count);
+ kfree(*blob);
+ *blob = NULL;
+ filp_close(fdtb, NULL);
+ return ret < 0 ? ret : -ENODEV;
+ }
+
+ filp_close(fdtb, NULL);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(load_dtb_to_mem);
+
+int str_to_u8_array(const char *p, u32 base, u8 array[])
+{
+ const char *start = p;
+ char str[12];
+ int length = 0;
+ int i, ret;
+
+ pr_info("input: %s", p);
+
+ for (i = 0 ; i < 255; i++) {
+ while (*p == ' ')
+ p++;
+ if (*p == '\0')
+ break;
+ start = p;
+
+ while ((*p != ' ') && (*p != '\0'))
+ p++;
+
+ if ((p - start) >= sizeof(str))
+ break;
+
+ memset(str, 0, sizeof(str));
+ memcpy(str, start, p - start);
+
+ ret = kstrtou8(str, base, &array[i]);
+ if (ret) {
+ DRM_ERROR("input format error\n");
+ break;
+ }
+
+ length++;
+ }
+
+ return length;
+}
+EXPORT_SYMBOL_GPL(str_to_u8_array);
+
+int dump_bmp32(const char *p, u32 width, u32 height,
+ bool noflip, const char *filename)
+{
+ struct file *fp;
+ mm_segment_t fs;
+ loff_t pos;
+ struct dib_header dib_header = {
+ .size = sizeof(dib_header),
+ .width = width,
+ .height = noflip ? -height : height,
+ .planes = 1,
+ .bpp = 32,
+ .compression = 3,
+ .data_size = 4 * width * height,
+ .h_res = 0xB13,
+ .v_res = 0xB13,
+ .colours = 0,
+ .important_colours = 0,
+ .red_mask = 0x000000FF,
+ .green_mask = 0x0000FF00,
+ .blue_mask = 0x00FF0000,
+ .alpha_mask = 0xFF000000,
+ .colour_space = 0x57696E20,
+ };
+ struct bmp_header bmp_header = {
+ .magic = 0x4d42,
+ .size = (width * height * 4) +
+ sizeof(bmp_header) + sizeof(dib_header),
+ .start = sizeof(bmp_header) + sizeof(dib_header),
+ };
+
+ fp = filp_open(filename, O_RDWR | O_CREAT, 0644);
+ if (IS_ERR(fp)) {
+ DRM_ERROR("failed to open %s: %ld\n", filename, PTR_ERR(fp));
+ return PTR_ERR(fp);
+ }
+
+ fs = get_fs();
+ set_fs(KERNEL_DS);
+ pos = 0;
+
+ vfs_write(fp, (const char *)&bmp_header, sizeof(bmp_header), &pos);
+ vfs_write(fp, (const char *)&dib_header, sizeof(dib_header), &pos);
+ vfs_write(fp, p, width * height * 4, &pos);
+
+ filp_close(fp, NULL);
+ set_fs(fs);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dump_bmp32);
+
+void *disp_ops_attach(const char *str, struct list_head *head)
+{
+ struct ops_list *list;
+ const char *ver;
+
+ list_for_each_entry(list, head, head) {
+ ver = list->entry->ver;
+ if (!strcmp(str, ver))
+ return list->entry->ops;
+ }
+
+ DRM_ERROR("attach disp ops %s failed\n", str);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(disp_ops_attach);
+
+int disp_ops_register(struct ops_entry *entry, struct list_head *head)
+{
+ struct ops_list *list;
+
+ list = kzalloc(sizeof(struct ops_list), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ list->entry = entry;
+ list_add(&list->head, head);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(disp_ops_register);
+
+struct device *sprd_disp_pipe_get_by_port(struct device *dev, int port)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *endpoint;
+ struct device_node *remote_node;
+ struct platform_device *remote_pdev;
+
+ endpoint = of_graph_get_endpoint_by_regs(np, port, 0);
+ if (!endpoint) {
+ DRM_ERROR("%s/port%d/endpoint0 was not found\n",
+ np->full_name, port);
+ return NULL;
+ }
+
+ remote_node = of_graph_get_remote_port_parent(endpoint);
+ if (!remote_node) {
+ DRM_ERROR("device node was not found by endpoint0\n");
+ return NULL;
+ }
+
+ remote_pdev = of_find_device_by_node(remote_node);
+ if (remote_pdev == NULL) {
+ DRM_ERROR("find %s platform device failed\n",
+ remote_node->full_name);
+ return NULL;
+ }
+
+ return &remote_pdev->dev;
+}
+EXPORT_SYMBOL_GPL(sprd_disp_pipe_get_by_port);
+
+struct device *sprd_disp_pipe_get_input(struct device *dev)
+{
+ return sprd_disp_pipe_get_by_port(dev, 1);
+}
+EXPORT_SYMBOL_GPL(sprd_disp_pipe_get_input);
+
+struct device *sprd_disp_pipe_get_output(struct device *dev)
+{
+ return sprd_disp_pipe_get_by_port(dev, 0);
+}
+EXPORT_SYMBOL_GPL(sprd_disp_pipe_get_output);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("Unisoc display common API library");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sprd/disp_lib.h b/drivers/gpu/drm/sprd/disp_lib.h
new file mode 100644
index 0000000..7900b89
--- /dev/null
+++ b/drivers/gpu/drm/sprd/disp_lib.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _DISP_LIB_H_
+#define _DISP_LIB_H_
+
+#include <linux/list.h>
+#include <drm/drm_print.h>
+
+#ifdef pr_fmt
+#undef pr_fmt
+#define pr_fmt(__fmt) "[drm][%20s] "__fmt, __func__
+#endif
+
+struct ops_entry {
+ const char *ver;
+ void *ops;
+};
+
+struct ops_list {
+ struct list_head head;
+ struct ops_entry *entry;
+};
+
+int str_to_u32_array(const char *p, u32 base, u32 array[]);
+int str_to_u8_array(const char *p, u32 base, u8 array[]);
+int dump_bmp32(const char *p, u32 width, u32 height,
+ bool bgra, const char *filename);
+int load_dtb_to_mem(const char *name, void **blob);
+
+void *disp_ops_attach(const char *str, struct list_head *head);
+int disp_ops_register(struct ops_entry *entry, struct list_head *head);
+
+struct device *sprd_disp_pipe_get_by_port(struct device *dev, int port);
+struct device *sprd_disp_pipe_get_input(struct device *dev);
+struct device *sprd_disp_pipe_get_output(struct device *dev);
+
+#endif
diff --git a/drivers/gpu/drm/sprd/dpu/Makefile b/drivers/gpu/drm/sprd/dpu/Makefile
new file mode 100644
index 0000000..d960107
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dpu/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+ifdef CONFIG_ARM64
+KBUILD_CFLAGS += -mstrict-align
+endif
+
+obj-y += dpu_r2p0.o
+
diff --git a/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
new file mode 100644
index 0000000..4c0a539
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
@@ -0,0 +1,1464 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include "sprd_dpu.h"
+
+#define DISPC_INT_FBC_PLD_ERR_MASK BIT(8)
+#define DISPC_INT_FBC_HDR_ERR_MASK BIT(9)
+
+#define DISPC_INT_MMU_INV_WR_MASK BIT(19)
+#define DISPC_INT_MMU_INV_RD_MASK BIT(18)
+#define DISPC_INT_MMU_VAOR_WR_MASK BIT(17)
+#define DISPC_INT_MMU_VAOR_RD_MASK BIT(16)
+
+#define XFBC8888_HEADER_SIZE(w, h) (ALIGN((w) * (h) / (8 * 8) / 2, 128))
+#define XFBC8888_PAYLOAD_SIZE(w, h) (w * h * 4)
+#define XFBC8888_BUFFER_SIZE(w, h) (XFBC8888_HEADER_SIZE(w, h) \
+ + XFBC8888_PAYLOAD_SIZE(w, h))
+
+#define XFBC565_HEADER_SIZE(w, h) (ALIGN((w) * (h) / (16 * 8) / 2, 128))
+#define XFBC565_PAYLOAD_SIZE(w, h) (w * h * 2)
+#define XFBC565_BUFFER_SIZE(w, h) (XFBC565_HEADER_SIZE(w, h) \
+ + XFBC565_PAYLOAD_SIZE(w, h))
+
+#define SLP_BRIGHTNESS_THRESHOLD 0x20
+
+struct layer_reg {
+ u32 addr[4];
+ u32 ctrl;
+ u32 size;
+ u32 pitch;
+ u32 pos;
+ u32 alpha;
+ u32 ck;
+ u32 pallete;
+ u32 crop_start;
+};
+
+struct wb_region_reg {
+ u32 pos;
+ u32 size;
+};
+
+struct dpu_reg {
+ u32 dpu_version;
+ u32 dpu_ctrl;
+ u32 dpu_cfg0;
+ u32 dpu_cfg1;
+ u32 dpu_cfg2;
+ u32 dpu_secure;
+ u32 reserved_0x0018_0x001C[2];
+ u32 panel_size;
+ u32 blend_size;
+ u32 reserved_0x0028;
+ u32 bg_color;
+ struct layer_reg layers[8];
+ u32 wb_base_addr;
+ u32 wb_ctrl;
+ u32 wb_cfg;
+ u32 wb_pitch;
+ struct wb_region_reg region[3];
+ u32 reserved_0x01D8_0x01DC[2];
+ u32 dpu_int_en;
+ u32 dpu_int_clr;
+ u32 dpu_int_sts;
+ u32 dpu_int_raw;
+ u32 dpi_ctrl;
+ u32 dpi_h_timing;
+ u32 dpi_v_timing;
+ u32 reserved_0x01FC;
+ u32 dpu_enhance_cfg;
+ u32 reserved_0x0204_0x020C[3];
+ u32 epf_epsilon;
+ u32 epf_gain0_3;
+ u32 epf_gain4_7;
+ u32 epf_diff;
+ u32 reserved_0x0220_0x023C[8];
+ u32 hsv_lut_addr;
+ u32 hsv_lut_wdata;
+ u32 hsv_lut_rdata;
+ u32 reserved_0x024C_0x027C[13];
+ u32 cm_coef01_00;
+ u32 cm_coef03_02;
+ u32 cm_coef11_10;
+ u32 cm_coef13_12;
+ u32 cm_coef21_20;
+ u32 cm_coef23_22;
+ u32 reserved_0x0298_0x02BC[10];
+ u32 slp_cfg0;
+ u32 slp_cfg1;
+ u32 reserved_0x02C8_0x02FC[14];
+ u32 gamma_lut_addr;
+ u32 gamma_lut_wdata;
+ u32 gamma_lut_rdata;
+ u32 reserved_0x030C_0x033C[13];
+ u32 checksum_en;
+ u32 checksum0_start_pos;
+ u32 checksum0_end_pos;
+ u32 checksum1_start_pos;
+ u32 checksum1_end_pos;
+ u32 checksum0_result;
+ u32 checksum1_result;
+ u32 reserved_0x035C;
+ u32 dpu_sts[18];
+ u32 reserved_0x03A8_0x03AC[2];
+ u32 dpu_fbc_cfg0;
+ u32 dpu_fbc_cfg1;
+ u32 reserved_0x03B8_0x03EC[14];
+ u32 rf_ram_addr;
+ u32 rf_ram_rdata_low;
+ u32 rf_ram_rdata_high;
+ u32 reserved_0x03FC_0x07FC[257];
+ u32 mmu_en;
+ u32 mmu_update;
+ u32 mmu_min_vpn;
+ u32 mmu_vpn_range;
+ u32 mmu_pt_addr;
+ u32 mmu_default_page;
+ u32 mmu_vaor_addr_rd;
+ u32 mmu_vaor_addr_wr;
+ u32 mmu_inv_addr_rd;
+ u32 mmu_inv_addr_wr;
+ u32 mmu_uns_addr_rd;
+ u32 mmu_uns_addr_wr;
+ u32 mmu_miss_cnt;
+ u32 mmu_pt_update_qos;
+ u32 mmu_version;
+ u32 mmu_min_ppn1;
+ u32 mmu_ppn_range1;
+ u32 mmu_min_ppn2;
+ u32 mmu_ppn_range2;
+ u32 mmu_vpn_paor_rd;
+ u32 mmu_vpn_paor_wr;
+ u32 mmu_ppn_paor_rd;
+ u32 mmu_ppn_paor_wr;
+ u32 mmu_reg_au_manage;
+ u32 mmu_page_rd_ch;
+ u32 mmu_page_wr_ch;
+ u32 mmu_read_page_cmd_cnt;
+ u32 mmu_read_page_latency_cnt;
+ u32 mmu_page_max_latency;
+};
+
+struct wb_region {
+ u32 index;
+ u16 pos_x;
+ u16 pos_y;
+ u16 size_w;
+ u16 size_h;
+};
+
+struct enhance_module {
+ u32 scl_en: 1;
+ u32 epf_en: 1;
+ u32 hsv_en: 1;
+ u32 cm_en: 1;
+ u32 slp_en: 1;
+ u32 gamma_en: 1;
+ u32 blp_en: 1;
+};
+
+struct scale_cfg {
+ u32 in_w;
+ u32 in_h;
+};
+
+struct epf_cfg {
+ u16 epsilon0;
+ u16 epsilon1;
+ u8 gain0;
+ u8 gain1;
+ u8 gain2;
+ u8 gain3;
+ u8 gain4;
+ u8 gain5;
+ u8 gain6;
+ u8 gain7;
+ u8 max_diff;
+ u8 min_diff;
+};
+
+struct hsv_entry {
+ u16 hue;
+ u16 sat;
+};
+
+struct hsv_lut {
+ struct hsv_entry table[360];
+};
+
+struct gamma_entry {
+ u16 r;
+ u16 g;
+ u16 b;
+};
+
+struct gamma_lut {
+ u16 r[256];
+ u16 g[256];
+ u16 b[256];
+};
+
+struct cm_cfg {
+ short coef00;
+ short coef01;
+ short coef02;
+ short coef03;
+ short coef10;
+ short coef11;
+ short coef12;
+ short coef13;
+ short coef20;
+ short coef21;
+ short coef22;
+ short coef23;
+};
+
+struct slp_cfg {
+ u8 brightness;
+ u8 conversion_matrix;
+ u8 brightness_step;
+ u8 second_bright_factor;
+ u8 first_percent_th;
+ u8 first_max_bright_th;
+};
+
+static struct scale_cfg scale_copy;
+static struct cm_cfg cm_copy;
+static struct slp_cfg slp_copy;
+static struct gamma_lut gamma_copy;
+static struct hsv_lut hsv_copy;
+static struct epf_cfg epf_copy;
+static u32 enhance_en;
+
+static DECLARE_WAIT_QUEUE_HEAD(wait_queue);
+static bool panel_ready = true;
+static bool need_scale;
+static bool mode_changed;
+static bool evt_update;
+static bool evt_stop;
+static u32 prev_y2r_coef;
+
+static void dpu_sr_config(struct dpu_context *ctx);
+static void dpu_enhance_reload(struct dpu_context *ctx);
+static void dpu_clean_all(struct dpu_context *ctx);
+static void dpu_layer(struct dpu_context *ctx,
+ struct sprd_dpu_layer *hwlayer);
+
+static u32 dpu_get_version(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ return reg->dpu_version;
+}
+
+static bool dpu_check_raw_int(struct dpu_context *ctx, u32 mask)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ u32 val;
+
+ down(&ctx->refresh_lock);
+ if (!ctx->is_inited) {
+ up(&ctx->refresh_lock);
+ pr_err("dpu is not initialized\n");
+ return false;
+ }
+
+ val = reg->dpu_int_raw;
+ up(&ctx->refresh_lock);
+
+ if (val & mask)
+ return true;
+
+ pr_err("dpu_int_raw:0x%x\n", val);
+ return false;
+}
+
+static int dpu_parse_dt(struct dpu_context *ctx,
+ struct device_node *np)
+{
+ return 0;
+}
+
+static void dpu_dump(struct dpu_context *ctx)
+{
+ u32 *reg = (u32 *)ctx->base;
+ int i;
+
+ pr_info(" 0 4 8 C\n");
+ for (i = 0; i < 256; i += 4) {
+ pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i + 3]);
+ }
+}
+
+static u32 check_mmu_isr(struct dpu_context *ctx, u32 reg_val)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ u32 mmu_mask = DISPC_INT_MMU_VAOR_RD_MASK |
+ DISPC_INT_MMU_VAOR_WR_MASK |
+ DISPC_INT_MMU_INV_RD_MASK |
+ DISPC_INT_MMU_INV_WR_MASK;
+ u32 val = reg_val & mmu_mask;
+
+ if (val) {
+ pr_err("--- iommu interrupt err: 0x%04x ---\n", val);
+
+ pr_err("iommu invalid read error, addr: 0x%08x\n",
+ reg->mmu_inv_addr_rd);
+ pr_err("iommu invalid write error, addr: 0x%08x\n",
+ reg->mmu_inv_addr_wr);
+ pr_err("iommu va out of range read error, addr: 0x%08x\n",
+ reg->mmu_vaor_addr_rd);
+ pr_err("iommu va out of range write error, addr: 0x%08x\n",
+ reg->mmu_vaor_addr_wr);
+ pr_err("BUG: iommu failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+
+ dpu_dump(ctx);
+ }
+
+ return val;
+}
+
+static u32 dpu_isr(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ u32 reg_val, int_mask = 0;
+
+ reg_val = reg->dpu_int_sts;
+
+ /* disable err interrupt */
+ if (reg_val & DISPC_INT_ERR_MASK)
+ int_mask |= DISPC_INT_ERR_MASK;
+
+ /* dpu update done isr */
+ if (reg_val & DISPC_INT_UPDATE_DONE_MASK) {
+ evt_update = true;
+ wake_up_interruptible_all(&wait_queue);
+ }
+
+ /* dpu stop done isr */
+ if (reg_val & DISPC_INT_DONE_MASK) {
+ evt_stop = true;
+ wake_up_interruptible_all(&wait_queue);
+ }
+
+ /* dpu ifbc payload error isr */
+ if (reg_val & DISPC_INT_FBC_PLD_ERR_MASK) {
+ int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
+ pr_err("dpu ifbc payload error\n");
+ }
+
+ /* dpu ifbc header error isr */
+ if (reg_val & DISPC_INT_FBC_HDR_ERR_MASK) {
+ int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
+ pr_err("dpu ifbc header error\n");
+ }
+
+ int_mask |= check_mmu_isr(ctx, reg_val);
+
+ reg->dpu_int_clr = reg_val;
+ reg->dpu_int_en &= ~int_mask;
+
+ return reg_val;
+}
+
+static int dpu_wait_stop_done(struct dpu_context *ctx)
+{
+ int rc;
+
+ if (ctx->is_stopped)
+ return 0;
+
+ rc = wait_event_interruptible_timeout(wait_queue, evt_stop,
+ msecs_to_jiffies(500));
+ evt_stop = false;
+
+ ctx->is_stopped = true;
+
+ if (!rc) {
+ pr_err("dpu wait for stop done time out!\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int dpu_wait_update_done(struct dpu_context *ctx)
+{
+ int rc;
+
+ evt_update = false;
+
+ rc = wait_event_interruptible_timeout(wait_queue, evt_update,
+ msecs_to_jiffies(500));
+
+ if (!rc) {
+ pr_err("dpu wait for reg update done time out!\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static void dpu_stop(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ if (ctx->if_type == SPRD_DISPC_IF_DPI)
+ reg->dpu_ctrl |= BIT(1);
+
+ dpu_wait_stop_done(ctx);
+ pr_info("dpu stop\n");
+}
+
+static void dpu_run(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ reg->dpu_ctrl |= BIT(0);
+
+ ctx->is_stopped = false;
+
+ pr_info("dpu run\n");
+
+ if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+ /*
+ * If the panel read GRAM speed faster than
+ * DSI write GRAM speed, it will display some
+ * mass on screen when backlight on. So wait
+ * a TE period after flush the GRAM.
+ */
+ if (!panel_ready) {
+ dpu_wait_stop_done(ctx);
+ /* wait for TE again */
+ mdelay(20);
+ panel_ready = true;
+ }
+ }
+}
+
+static int dpu_init(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ u32 size;
+
+ reg->bg_color = 0;
+
+ size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
+ reg->panel_size = size;
+ reg->blend_size = size;
+
+ reg->dpu_cfg0 = BIT(4) | BIT(5);
+ prev_y2r_coef = 3;
+
+ reg->dpu_cfg1 = 0x004466da;
+ reg->dpu_cfg2 = 0;
+
+ if (ctx->is_stopped)
+ dpu_clean_all(ctx);
+
+ reg->mmu_en = 0;
+ reg->mmu_min_ppn1 = 0;
+ reg->mmu_ppn_range1 = 0xffff;
+ reg->mmu_min_ppn2 = 0;
+ reg->mmu_ppn_range2 = 0xffff;
+ reg->mmu_vpn_range = 0x1ffff;
+
+ reg->dpu_int_clr = 0xffff;
+
+ dpu_enhance_reload(ctx);
+
+ return 0;
+}
+
+static void dpu_uninit(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ reg->dpu_int_en = 0;
+ reg->dpu_int_clr = 0xff;
+
+ panel_ready = false;
+}
+
+enum {
+ DPU_LAYER_FORMAT_YUV422_2PLANE,
+ DPU_LAYER_FORMAT_YUV420_2PLANE,
+ DPU_LAYER_FORMAT_YUV420_3PLANE,
+ DPU_LAYER_FORMAT_ARGB8888,
+ DPU_LAYER_FORMAT_RGB565,
+ DPU_LAYER_FORMAT_XFBC_ARGB8888 = 8,
+ DPU_LAYER_FORMAT_XFBC_RGB565,
+ DPU_LAYER_FORMAT_MAX_TYPES,
+};
+
+enum {
+ DPU_LAYER_ROTATION_0,
+ DPU_LAYER_ROTATION_90,
+ DPU_LAYER_ROTATION_180,
+ DPU_LAYER_ROTATION_270,
+ DPU_LAYER_ROTATION_0_M,
+ DPU_LAYER_ROTATION_90_M,
+ DPU_LAYER_ROTATION_180_M,
+ DPU_LAYER_ROTATION_270_M,
+};
+
+static u32 to_dpu_rotation(u32 angle)
+{
+ u32 rot = DPU_LAYER_ROTATION_0;
+
+ switch (angle) {
+ case 0:
+ case DRM_MODE_ROTATE_0:
+ rot = DPU_LAYER_ROTATION_0;
+ break;
+ case DRM_MODE_ROTATE_90:
+ rot = DPU_LAYER_ROTATION_90;
+ break;
+ case DRM_MODE_ROTATE_180:
+ rot = DPU_LAYER_ROTATION_180;
+ break;
+ case DRM_MODE_ROTATE_270:
+ rot = DPU_LAYER_ROTATION_270;
+ break;
+ case DRM_MODE_REFLECT_Y:
+ rot = DPU_LAYER_ROTATION_180_M;
+ break;
+ case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
+ rot = DPU_LAYER_ROTATION_90_M;
+ break;
+ case DRM_MODE_REFLECT_X:
+ rot = DPU_LAYER_ROTATION_0_M;
+ break;
+ case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
+ rot = DPU_LAYER_ROTATION_270_M;
+ break;
+ default:
+ pr_err("rotation convert unsupport angle (drm)= 0x%x\n", angle);
+ break;
+ }
+
+ return rot;
+}
+
+static u32 dpu_img_ctrl(u32 format, u32 blending, u32 compression, u32 rotation)
+{
+ int reg_val = 0;
+
+ /* layer enable */
+ reg_val |= BIT(0);
+
+ switch (format) {
+ case DRM_FORMAT_BGRA8888:
+ /* BGRA8888 -> ARGB8888 */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
+ if (compression)
+ /* XFBC-ARGB8888 */
+ reg_val |= (DPU_LAYER_FORMAT_XFBC_ARGB8888 << 4);
+ else
+ reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
+ break;
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_RGBA8888:
+ /* RGBA8888 -> ABGR8888 */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
+ case DRM_FORMAT_ABGR8888:
+ /* rb switch */
+ reg_val |= BIT(10);
+ case DRM_FORMAT_ARGB8888:
+ if (compression)
+ /* XFBC-ARGB8888 */
+ reg_val |= (DPU_LAYER_FORMAT_XFBC_ARGB8888 << 4);
+ else
+ reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
+ break;
+ case DRM_FORMAT_XBGR8888:
+ /* rb switch */
+ reg_val |= BIT(10);
+ case DRM_FORMAT_XRGB8888:
+ if (compression)
+ /* XFBC-ARGB8888 */
+ reg_val |= (DPU_LAYER_FORMAT_XFBC_ARGB8888 << 4);
+ else
+ reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
+ break;
+ case DRM_FORMAT_BGR565:
+ /* rb switch */
+ reg_val |= BIT(10);
+ case DRM_FORMAT_RGB565:
+ if (compression)
+ /* XFBC-RGB565 */
+ reg_val |= (DPU_LAYER_FORMAT_XFBC_RGB565 << 4);
+ else
+ reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
+ break;
+ case DRM_FORMAT_NV12:
+ /* 2-Lane: Yuv420 */
+ reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
+ /* Y endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+ /* UV endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
+ break;
+ case DRM_FORMAT_NV21:
+ /* 2-Lane: Yuv420 */
+ reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
+ /* Y endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+ /* UV endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
+ break;
+ case DRM_FORMAT_NV16:
+ /* 2-Lane: Yuv422 */
+ reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
+ /* Y endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
+ /* UV endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
+ break;
+ case DRM_FORMAT_NV61:
+ /* 2-Lane: Yuv422 */
+ reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
+ /* Y endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+ /* UV endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
+ break;
+ case DRM_FORMAT_YUV420:
+ reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
+ /* Y endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+ /* UV endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
+ break;
+ case DRM_FORMAT_YVU420:
+ reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
+ /* Y endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+ /* UV endian */
+ reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
+ break;
+ default:
+ pr_err("error: invalid format %c%c%c%c\n", format,
+ format >> 8,
+ format >> 16,
+ format >> 24);
+ break;
+ }
+
+ switch (blending) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ /* don't do blending, maybe RGBX */
+ /* alpha mode select - layer alpha */
+ reg_val |= BIT(2);
+ break;
+ case DRM_MODE_BLEND_COVERAGE:
+ /* alpha mode select - combo alpha */
+ reg_val |= BIT(3);
+ /*Normal mode*/
+ reg_val &= (~BIT(16));
+ break;
+ case DRM_MODE_BLEND_PREMULTI:
+ /* alpha mode select - combo alpha */
+ reg_val |= BIT(3);
+ /*Pre-mult mode*/
+ reg_val |= BIT(16);
+ break;
+ default:
+ /* alpha mode select - layer alpha */
+ reg_val |= BIT(2);
+ break;
+ }
+
+ rotation = to_dpu_rotation(rotation);
+ reg_val |= (rotation & 0x7) << 20;
+
+ return reg_val;
+}
+
+static int check_layer_y2r_coef(struct sprd_dpu_layer layers[], u8 count)
+{
+ int i;
+
+ for (i = (count - 1); i >= 0; i--) {
+ switch (layers[i].format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_YUV420:
+ case DRM_FORMAT_YVU420:
+ if (layers[i].y2r_coef == prev_y2r_coef)
+ return -EINVAL;
+
+ /* need to config dpu y2r coef */
+ prev_y2r_coef = layers[i].y2r_coef;
+ return prev_y2r_coef;
+ default:
+ break;
+ }
+ }
+
+ /* not find yuv layer */
+ return -EINVAL;
+}
+
+static void dpu_clean_all(struct dpu_context *ctx)
+{
+ int i;
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ for (i = 0; i < 8; i++)
+ reg->layers[i].ctrl = 0;
+}
+
+static void dpu_bgcolor(struct dpu_context *ctx, u32 color)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ if (ctx->if_type == SPRD_DISPC_IF_EDPI)
+ dpu_wait_stop_done(ctx);
+
+ reg->bg_color = color;
+
+ dpu_clean_all(ctx);
+
+ if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->is_stopped) {
+ reg->dpu_ctrl |= BIT(2);
+ dpu_wait_update_done(ctx);
+ } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+ reg->dpu_ctrl |= BIT(0);
+ ctx->is_stopped = false;
+ }
+}
+
+static void dpu_layer(struct dpu_context *ctx,
+ struct sprd_dpu_layer *hwlayer)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ const struct drm_format_info *info;
+ struct layer_reg *layer;
+ u32 addr, size, offset;
+ int i;
+
+ layer = &reg->layers[hwlayer->index];
+ offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
+
+ if (hwlayer->pallete_en) {
+ size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
+ layer->pos = offset;
+ layer->size = size;
+ layer->alpha = hwlayer->alpha;
+ layer->pallete = hwlayer->pallete_color;
+
+ /* pallete layer enable */
+ layer->ctrl = 0x1005;
+
+ pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
+ hwlayer->dst_x, hwlayer->dst_y,
+ hwlayer->dst_w, hwlayer->dst_h);
+ return;
+ }
+
+ if (hwlayer->src_w && hwlayer->src_h)
+ size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) << 16);
+ else
+ size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
+
+ for (i = 0; i < hwlayer->planes; i++) {
+ addr = hwlayer->addr[i];
+
+ /* dpu r2p0 just support xfbc-rgb */
+ if (hwlayer->xfbc)
+ addr += hwlayer->header_size_r;
+
+ if (addr % 16)
+ pr_err("layer addr[%d] is not 16 bytes align, it's 0x%08x\n",
+ i, addr);
+ layer->addr[i] = addr;
+ }
+
+ layer->pos = offset;
+ layer->size = size;
+ layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
+ layer->alpha = hwlayer->alpha;
+
+ info = drm_format_info(hwlayer->format);
+ if (info->cpp[0] == 0) {
+ pr_err("layer[%d] bytes per pixel is invalid\n", hwlayer->index);
+ return;
+ }
+
+ if (hwlayer->planes == 3)
+ /* UV pitch is 1/2 of Y pitch*/
+ layer->pitch = (hwlayer->pitch[0] / info->cpp[0]) |
+ (hwlayer->pitch[0] / info->cpp[0] << 15);
+ else
+ layer->pitch = hwlayer->pitch[0] / info->cpp[0];
+
+ layer->ctrl = dpu_img_ctrl(hwlayer->format, hwlayer->blending,
+ hwlayer->xfbc, hwlayer->rotation);
+
+ pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
+ hwlayer->dst_x, hwlayer->dst_y,
+ hwlayer->dst_w, hwlayer->dst_h);
+ pr_debug("start_x = %d, start_y = %d, start_w = %d, start_h = %d\n",
+ hwlayer->src_x, hwlayer->src_y,
+ hwlayer->src_w, hwlayer->src_h);
+}
+
+static void dpu_scaling(struct dpu_context *ctx,
+ struct sprd_dpu_layer layers[], u8 count)
+{
+ int i;
+ struct sprd_dpu_layer *top_layer;
+
+ if (mode_changed) {
+ top_layer = &layers[count - 1];
+ pr_debug("------------------------------------\n");
+ for (i = 0; i < count; i++) {
+ pr_debug("layer[%d] : %dx%d --- (%d)\n", i,
+ layers[i].dst_w, layers[i].dst_h,
+ scale_copy.in_w);
+ }
+
+ if (top_layer->dst_w <= scale_copy.in_w) {
+ dpu_sr_config(ctx);
+ mode_changed = false;
+
+ pr_info("do scaling enhace: 0x%x, top layer(%dx%d)\n",
+ enhance_en, top_layer->dst_w,
+ top_layer->dst_h);
+ }
+ }
+}
+
+static void dpu_flip(struct dpu_context *ctx,
+ struct sprd_dpu_layer layers[], u8 count)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ int i;
+ int y2r_coef;
+
+ /*
+ * Make sure the dpu is in stop status. DPU_R2P0 has no shadow
+ * registers in EDPI mode. So the config registers can only be
+ * updated in the rising edge of DPU_RUN bit.
+ */
+ if (ctx->if_type == SPRD_DISPC_IF_EDPI)
+ dpu_wait_stop_done(ctx);
+
+ /* set Y2R conversion coef */
+ y2r_coef = check_layer_y2r_coef(layers, count);
+ if (y2r_coef >= 0) {
+ /* write dpu_cfg0 register after dpu is in idle status */
+ if (ctx->if_type == SPRD_DISPC_IF_DPI)
+ dpu_stop(ctx);
+
+ reg->dpu_cfg0 &= ~(0x7 << 4);
+ reg->dpu_cfg0 |= (y2r_coef << 4);
+ }
+
+ /* reset the bgcolor to black */
+ reg->bg_color = 0;
+
+ /* disable all the layers */
+ dpu_clean_all(ctx);
+
+ /* to check if dpu need scaling the frame for SR */
+ dpu_scaling(ctx, layers, count);
+
+ /* start configure dpu layers */
+ for (i = 0; i < count; i++)
+ dpu_layer(ctx, &layers[i]);
+
+ /* update trigger and wait */
+ if (ctx->if_type == SPRD_DISPC_IF_DPI) {
+ if (!ctx->is_stopped) {
+ reg->dpu_ctrl |= BIT(2);
+ dpu_wait_update_done(ctx);
+ } else if (y2r_coef >= 0) {
+ reg->dpu_ctrl |= BIT(0);
+ ctx->is_stopped = false;
+ pr_info("dpu start\n");
+ }
+
+ reg->dpu_int_en |= DISPC_INT_ERR_MASK;
+
+ } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+ reg->dpu_ctrl |= BIT(0);
+
+ ctx->is_stopped = false;
+ }
+
+ /*
+ * If the following interrupt was disabled in isr,
+ * re-enable it.
+ */
+ reg->dpu_int_en |= DISPC_INT_FBC_PLD_ERR_MASK |
+ DISPC_INT_FBC_HDR_ERR_MASK |
+ DISPC_INT_MMU_VAOR_RD_MASK |
+ DISPC_INT_MMU_VAOR_WR_MASK |
+ DISPC_INT_MMU_INV_RD_MASK |
+ DISPC_INT_MMU_INV_WR_MASK;
+}
+
+static void dpu_dpi_init(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ u32 int_mask = 0;
+
+ if (ctx->if_type == SPRD_DISPC_IF_DPI) {
+ /* use dpi as interface */
+ reg->dpu_cfg0 &= ~BIT(0);
+
+ /* disable Halt function for SPRD DSI */
+ reg->dpi_ctrl &= ~BIT(16);
+
+ /* select te from external pad */
+ reg->dpi_ctrl |= BIT(10);
+
+ /* set dpi timing */
+ reg->dpi_h_timing = (ctx->vm.hsync_len << 0) |
+ (ctx->vm.hback_porch << 8) |
+ (ctx->vm.hfront_porch << 20);
+ reg->dpi_v_timing = (ctx->vm.vsync_len << 0) |
+ (ctx->vm.vback_porch << 8) |
+ (ctx->vm.vfront_porch << 20);
+ if (ctx->vm.vsync_len + ctx->vm.vback_porch < 32)
+ pr_warn("Warning: (vsync + vbp) < 32, "
+ "underflow risk!\n");
+
+ /* enable dpu update done INT */
+ int_mask |= DISPC_INT_UPDATE_DONE_MASK;
+ /* enable dpu DONE INT */
+ int_mask |= DISPC_INT_DONE_MASK;
+ /* enable dpu dpi vsync */
+ int_mask |= DISPC_INT_DPI_VSYNC_MASK;
+ /* enable dpu TE INT */
+ int_mask |= DISPC_INT_TE_MASK;
+ /* enable underflow err INT */
+ int_mask |= DISPC_INT_ERR_MASK;
+ /* enable write back done INT */
+ int_mask |= DISPC_INT_WB_DONE_MASK;
+ /* enable write back fail INT */
+ int_mask |= DISPC_INT_WB_FAIL_MASK;
+
+ } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+ /* use edpi as interface */
+ reg->dpu_cfg0 |= BIT(0);
+
+ /* use external te */
+ reg->dpi_ctrl |= BIT(10);
+
+ /* enable te */
+ reg->dpi_ctrl |= BIT(8);
+
+ /* enable stop DONE INT */
+ int_mask |= DISPC_INT_DONE_MASK;
+ /* enable TE INT */
+ int_mask |= DISPC_INT_TE_MASK;
+ }
+
+ /* enable ifbc payload error INT */
+ int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
+ /* enable ifbc header error INT */
+ int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
+ /* enable iommu va out of range read error INT */
+ int_mask |= DISPC_INT_MMU_VAOR_RD_MASK;
+ /* enable iommu va out of range write error INT */
+ int_mask |= DISPC_INT_MMU_VAOR_WR_MASK;
+ /* enable iommu invalid read error INT */
+ int_mask |= DISPC_INT_MMU_INV_RD_MASK;
+ /* enable iommu invalid write error INT */
+ int_mask |= DISPC_INT_MMU_INV_WR_MASK;
+
+ reg->dpu_int_en = int_mask;
+}
+
+static void enable_vsync(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ reg->dpu_int_en |= DISPC_INT_DPI_VSYNC_MASK;
+}
+
+static void disable_vsync(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ reg->dpu_int_en &= ~DISPC_INT_DPI_VSYNC_MASK;
+}
+
+static void dpu_enhance_backup(u32 id, void *param)
+{
+ u32 *p;
+
+ switch (id) {
+ case ENHANCE_CFG_ID_ENABLE:
+ p = param;
+ enhance_en |= *p;
+ pr_info("enhance enable backup: 0x%x\n", *p);
+ break;
+ case ENHANCE_CFG_ID_DISABLE:
+ p = param;
+ enhance_en &= ~(*p);
+ if (*p & BIT(1))
+ memset(&epf_copy, 0, sizeof(epf_copy));
+ pr_info("enhance disable backup: 0x%x\n", *p);
+ break;
+ case ENHANCE_CFG_ID_SCL:
+ memcpy(&scale_copy, param, sizeof(scale_copy));
+ enhance_en |= BIT(0);
+ pr_info("enhance scaling backup\n");
+ break;
+ case ENHANCE_CFG_ID_HSV:
+ memcpy(&hsv_copy, param, sizeof(hsv_copy));
+ enhance_en |= BIT(2);
+ pr_info("enhance hsv backup\n");
+ break;
+ case ENHANCE_CFG_ID_CM:
+ memcpy(&cm_copy, param, sizeof(cm_copy));
+ enhance_en |= BIT(3);
+ pr_info("enhance cm backup\n");
+ break;
+ case ENHANCE_CFG_ID_SLP:
+ memcpy(&slp_copy, param, sizeof(slp_copy));
+ enhance_en |= BIT(4);
+ pr_info("enhance slp backup\n");
+ break;
+ case ENHANCE_CFG_ID_GAMMA:
+ memcpy(&gamma_copy, param, sizeof(gamma_copy));
+ enhance_en |= BIT(5);
+ pr_info("enhance gamma backup\n");
+ break;
+ case ENHANCE_CFG_ID_EPF:
+ memcpy(&epf_copy, param, sizeof(epf_copy));
+ if ((slp_copy.brightness > SLP_BRIGHTNESS_THRESHOLD) ||
+ (enhance_en & BIT(0))) {
+ enhance_en |= BIT(1);
+ pr_info("enhance epf backup\n");
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void dpu_epf_set(struct dpu_reg *reg, struct epf_cfg *epf)
+{
+ reg->epf_epsilon = (epf->epsilon1 << 16) | epf->epsilon0;
+ reg->epf_gain0_3 = (epf->gain3 << 24) | (epf->gain2 << 16) |
+ (epf->gain1 << 8) | epf->gain0;
+ reg->epf_gain4_7 = (epf->gain7 << 24) | (epf->gain6 << 16) |
+ (epf->gain5 << 8) | epf->gain4;
+ reg->epf_diff = (epf->max_diff << 8) | epf->min_diff;
+}
+
+static void dpu_enhance_set(struct dpu_context *ctx, u32 id, void *param)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ struct scale_cfg *scale;
+ struct cm_cfg *cm;
+ struct slp_cfg *slp;
+ struct gamma_lut *gamma;
+ struct hsv_lut *hsv;
+ struct epf_cfg *epf;
+ u32 *p, i;
+
+ if (!ctx->is_inited) {
+ dpu_enhance_backup(id, param);
+ return;
+ }
+
+ if (ctx->if_type == SPRD_DISPC_IF_EDPI)
+ dpu_wait_stop_done(ctx);
+
+ switch (id) {
+ case ENHANCE_CFG_ID_ENABLE:
+ p = param;
+ reg->dpu_enhance_cfg |= *p;
+ pr_info("enhance module enable: 0x%x\n", *p);
+ break;
+ case ENHANCE_CFG_ID_DISABLE:
+ p = param;
+ reg->dpu_enhance_cfg &= ~(*p);
+ if (*p & BIT(1))
+ memset(&epf_copy, 0, sizeof(epf_copy));
+ pr_info("enhance module disable: 0x%x\n", *p);
+ break;
+ case ENHANCE_CFG_ID_SCL:
+ memcpy(&scale_copy, param, sizeof(scale_copy));
+ scale = &scale_copy;
+ reg->blend_size = (scale->in_h << 16) | scale->in_w;
+ reg->dpu_enhance_cfg |= BIT(0);
+ pr_info("enhance scaling: %ux%u\n", scale->in_w, scale->in_h);
+ break;
+ case ENHANCE_CFG_ID_HSV:
+ memcpy(&hsv_copy, param, sizeof(hsv_copy));
+ hsv = &hsv_copy;
+ for (i = 0; i < 360; i++) {
+ reg->hsv_lut_addr = i;
+ udelay(1);
+ reg->hsv_lut_wdata = (hsv->table[i].sat << 16) |
+ hsv->table[i].hue;
+ }
+ reg->dpu_enhance_cfg |= BIT(2);
+ pr_info("enhance hsv set\n");
+ break;
+ case ENHANCE_CFG_ID_CM:
+ memcpy(&cm_copy, param, sizeof(cm_copy));
+ cm = &cm_copy;
+ reg->cm_coef01_00 = (cm->coef01 << 16) | cm->coef00;
+ reg->cm_coef03_02 = (cm->coef03 << 16) | cm->coef02;
+ reg->cm_coef11_10 = (cm->coef11 << 16) | cm->coef10;
+ reg->cm_coef13_12 = (cm->coef13 << 16) | cm->coef12;
+ reg->cm_coef21_20 = (cm->coef21 << 16) | cm->coef20;
+ reg->cm_coef23_22 = (cm->coef23 << 16) | cm->coef22;
+ reg->dpu_enhance_cfg |= BIT(3);
+ pr_info("enhance cm set\n");
+ break;
+ case ENHANCE_CFG_ID_SLP:
+ memcpy(&slp_copy, param, sizeof(slp_copy));
+ slp = &slp_copy;
+ reg->slp_cfg0 = (slp->second_bright_factor << 24) |
+ (slp->brightness_step << 16) |
+ (slp->conversion_matrix << 8) |
+ slp->brightness;
+ reg->slp_cfg1 = (slp->first_max_bright_th << 8) |
+ slp->first_percent_th;
+ reg->dpu_enhance_cfg |= BIT(4);
+ pr_info("enhance slp set\n");
+ break;
+ case ENHANCE_CFG_ID_GAMMA:
+ memcpy(&gamma_copy, param, sizeof(gamma_copy));
+ gamma = &gamma_copy;
+ for (i = 0; i < 256; i++) {
+ reg->gamma_lut_addr = i;
+ udelay(1);
+ reg->gamma_lut_wdata = (gamma->r[i] << 20) |
+ (gamma->g[i] << 10) |
+ gamma->b[i];
+ pr_debug("0x%02x: r=%u, g=%u, b=%u\n", i,
+ gamma->r[i], gamma->g[i], gamma->b[i]);
+ }
+ reg->dpu_enhance_cfg |= BIT(5);
+ pr_info("enhance gamma set\n");
+ break;
+ case ENHANCE_CFG_ID_EPF:
+ memcpy(&epf_copy, param, sizeof(epf_copy));
+ if ((slp_copy.brightness > SLP_BRIGHTNESS_THRESHOLD) ||
+ (enhance_en & BIT(0))) {
+ epf = &epf_copy;
+ dpu_epf_set(reg, epf);
+ reg->dpu_enhance_cfg |= BIT(1);
+ pr_info("enhance epf set\n");
+ break;
+ }
+ return;
+ default:
+ break;
+ }
+
+ if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->is_stopped) {
+ reg->dpu_ctrl |= BIT(2);
+ dpu_wait_update_done(ctx);
+ } else if ((ctx->if_type == SPRD_DISPC_IF_EDPI) && panel_ready) {
+ /*
+ * In EDPI mode, we need to wait panel initializatin
+ * completed. Otherwise, the dpu enhance settings may
+ * start before panel initialization.
+ */
+ reg->dpu_ctrl |= BIT(0);
+ ctx->is_stopped = false;
+ }
+
+ enhance_en = reg->dpu_enhance_cfg;
+}
+
+static void dpu_enhance_get(struct dpu_context *ctx, u32 id, void *param)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ struct scale_cfg *scale;
+ struct epf_cfg *ep;
+ struct slp_cfg *slp;
+ struct gamma_lut *gamma;
+ u32 *p32;
+ int i, val;
+
+ switch (id) {
+ case ENHANCE_CFG_ID_ENABLE:
+ p32 = param;
+ *p32 = reg->dpu_enhance_cfg;
+ pr_info("enhance module enable get\n");
+ break;
+ case ENHANCE_CFG_ID_SCL:
+ scale = param;
+ val = reg->blend_size;
+ scale->in_w = val & 0xffff;
+ scale->in_h = val >> 16;
+ pr_info("enhance scaling get\n");
+ break;
+ case ENHANCE_CFG_ID_EPF:
+ ep = param;
+
+ val = reg->epf_epsilon;
+ ep->epsilon0 = val;
+ ep->epsilon1 = val >> 16;
+
+ val = reg->epf_gain0_3;
+ ep->gain0 = val;
+ ep->gain1 = val >> 8;
+ ep->gain2 = val >> 16;
+ ep->gain3 = val >> 24;
+
+ val = reg->epf_gain4_7;
+ ep->gain4 = val;
+ ep->gain5 = val >> 8;
+ ep->gain6 = val >> 16;
+ ep->gain7 = val >> 24;
+
+ val = reg->epf_diff;
+ ep->min_diff = val;
+ ep->max_diff = val >> 8;
+ pr_info("enhance epf get\n");
+ break;
+ case ENHANCE_CFG_ID_HSV:
+ dpu_stop(ctx);
+ p32 = param;
+ for (i = 0; i < 360; i++) {
+ reg->hsv_lut_addr = i;
+ udelay(1);
+ *p32++ = reg->hsv_lut_rdata;
+ }
+ dpu_run(ctx);
+ pr_info("enhance hsv get\n");
+ break;
+ case ENHANCE_CFG_ID_CM:
+ p32 = param;
+ *p32++ = reg->cm_coef01_00;
+ *p32++ = reg->cm_coef03_02;
+ *p32++ = reg->cm_coef11_10;
+ *p32++ = reg->cm_coef13_12;
+ *p32++ = reg->cm_coef21_20;
+ *p32++ = reg->cm_coef23_22;
+ pr_info("enhance cm get\n");
+ break;
+ case ENHANCE_CFG_ID_SLP:
+ slp = param;
+
+ val = reg->slp_cfg0;
+ slp->brightness = val;
+ slp->conversion_matrix = val >> 8;
+ slp->brightness_step = val >> 16;
+ slp->second_bright_factor = val >> 24;
+
+ val = reg->slp_cfg1;
+ slp->first_percent_th = val;
+ slp->first_max_bright_th = val >> 8;
+ pr_info("enhance slp get\n");
+ break;
+ case ENHANCE_CFG_ID_GAMMA:
+ dpu_stop(ctx);
+ gamma = param;
+ for (i = 0; i < 256; i++) {
+ reg->gamma_lut_addr = i;
+ udelay(1);
+ val = reg->gamma_lut_rdata;
+ gamma->r[i] = (val >> 20) & 0x3FF;
+ gamma->g[i] = (val >> 10) & 0x3FF;
+ gamma->b[i] = val & 0x3FF;
+ pr_debug("0x%02x: r=%u, g=%u, b=%u\n", i,
+ gamma->r[i], gamma->g[i], gamma->b[i]);
+ }
+ dpu_run(ctx);
+ pr_info("enhance gamma get\n");
+ break;
+ default:
+ break;
+ }
+}
+
+static void dpu_enhance_reload(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+ struct scale_cfg *scale;
+ struct cm_cfg *cm;
+ struct slp_cfg *slp;
+ struct gamma_lut *gamma;
+ struct hsv_lut *hsv;
+ struct epf_cfg *epf;
+ int i;
+
+ if (enhance_en & BIT(0)) {
+ scale = &scale_copy;
+ reg->blend_size = (scale->in_h << 16) | scale->in_w;
+ pr_info("enhance scaling from %ux%u to %ux%u\n", scale->in_w,
+ scale->in_h, ctx->vm.hactive, ctx->vm.vactive);
+ }
+
+ if (enhance_en & BIT(1)) {
+ epf = &epf_copy;
+ dpu_epf_set(reg, epf);
+ pr_info("enhance epf reload\n");
+ }
+
+ if (enhance_en & BIT(2)) {
+ hsv = &hsv_copy;
+ for (i = 0; i < 360; i++) {
+ reg->hsv_lut_addr = i;
+ udelay(1);
+ reg->hsv_lut_wdata = (hsv->table[i].sat << 16) |
+ hsv->table[i].hue;
+ }
+ pr_info("enhance hsv reload\n");
+ }
+
+ if (enhance_en & BIT(3)) {
+ cm = &cm_copy;
+ reg->cm_coef01_00 = (cm->coef01 << 16) | cm->coef00;
+ reg->cm_coef03_02 = (cm->coef03 << 16) | cm->coef02;
+ reg->cm_coef11_10 = (cm->coef11 << 16) | cm->coef10;
+ reg->cm_coef13_12 = (cm->coef13 << 16) | cm->coef12;
+ reg->cm_coef21_20 = (cm->coef21 << 16) | cm->coef20;
+ reg->cm_coef23_22 = (cm->coef23 << 16) | cm->coef22;
+ pr_info("enhance cm reload\n");
+ }
+
+ if (enhance_en & BIT(4)) {
+ slp = &slp_copy;
+ reg->slp_cfg0 = (slp->second_bright_factor << 24) |
+ (slp->brightness_step << 16) |
+ (slp->conversion_matrix << 8) |
+ slp->brightness;
+ reg->slp_cfg1 = (slp->first_max_bright_th << 8) |
+ slp->first_percent_th;
+ pr_info("enhance slp reload\n");
+ }
+
+ if (enhance_en & BIT(5)) {
+ gamma = &gamma_copy;
+ for (i = 0; i < 256; i++) {
+ reg->gamma_lut_addr = i;
+ udelay(1);
+ reg->gamma_lut_wdata = (gamma->r[i] << 20) |
+ (gamma->g[i] << 10) |
+ gamma->b[i];
+ pr_debug("0x%02x: r=%u, g=%u, b=%u\n", i,
+ gamma->r[i], gamma->g[i], gamma->b[i]);
+ }
+ pr_info("enhance gamma reload\n");
+ }
+
+ reg->dpu_enhance_cfg = enhance_en;
+}
+
+static void dpu_sr_config(struct dpu_context *ctx)
+{
+ struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+ reg->blend_size = (scale_copy.in_h << 16) | scale_copy.in_w;
+ if (need_scale) {
+ /* SLP is disabled mode or bypass mode */
+ if (slp_copy.brightness <= SLP_BRIGHTNESS_THRESHOLD) {
+
+ /*
+ * valid range of gain3 is [128,255];dpu_scaling maybe
+ * called before epf_copy is assinged a value
+ */
+ if (epf_copy.gain3 > 0) {
+ dpu_epf_set(reg, &epf_copy);
+ enhance_en |= BIT(1);
+ }
+ }
+ enhance_en |= BIT(0);
+ reg->dpu_enhance_cfg = enhance_en;
+ } else {
+ if (slp_copy.brightness <= SLP_BRIGHTNESS_THRESHOLD)
+ enhance_en &= ~(BIT(1));
+
+ enhance_en &= ~(BIT(0));
+ reg->dpu_enhance_cfg = enhance_en;
+ }
+}
+
+static int dpu_modeset(struct dpu_context *ctx,
+ struct drm_mode_modeinfo *mode)
+{
+ scale_copy.in_w = mode->hdisplay;
+ scale_copy.in_h = mode->vdisplay;
+
+ if ((mode->hdisplay != ctx->vm.hactive) ||
+ (mode->vdisplay != ctx->vm.vactive))
+ need_scale = true;
+ else
+ need_scale = false;
+
+ mode_changed = true;
+ pr_info("begin switch to %u x %u\n", mode->hdisplay, mode->vdisplay);
+
+ return 0;
+}
+
+static const u32 primary_fmts[] = {
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
+ DRM_FORMAT_NV12, DRM_FORMAT_NV21,
+ DRM_FORMAT_NV16, DRM_FORMAT_NV61,
+ DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
+};
+
+static int dpu_capability(struct dpu_context *ctx,
+ struct dpu_capability *cap)
+{
+ if (!cap)
+ return -EINVAL;
+
+ cap->max_layers = 6;
+ cap->fmts_ptr = primary_fmts;
+ cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
+
+ return 0;
+}
+
+static struct dpu_core_ops dpu_r2p0_ops = {
+ .parse_dt = dpu_parse_dt,
+ .version = dpu_get_version,
+ .init = dpu_init,
+ .uninit = dpu_uninit,
+ .run = dpu_run,
+ .stop = dpu_stop,
+ .isr = dpu_isr,
+ .ifconfig = dpu_dpi_init,
+ .capability = dpu_capability,
+ .flip = dpu_flip,
+ .bg_color = dpu_bgcolor,
+ .enable_vsync = enable_vsync,
+ .disable_vsync = disable_vsync,
+ .enhance_set = dpu_enhance_set,
+ .enhance_get = dpu_enhance_get,
+ .modeset = dpu_modeset,
+ .check_raw_int = dpu_check_raw_int,
+};
+
+static struct ops_entry entry = {
+ .ver = "dpu-r2p0",
+ .ops = &dpu_r2p0_ops,
+};
+
+static int __init dpu_core_register(void)
+{
+ return dpu_core_ops_register(&entry);
+}
+
+subsys_initcall(dpu_core_register);
diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c b/drivers/gpu/drm/sprd/sprd_dpu.c
new file mode 100644
index 0000000..43142b3
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dpu.c
@@ -0,0 +1,1152 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/component.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "sprd_drm.h"
+#include "sprd_dpu.h"
+#include "sprd_gem.h"
+
+struct sprd_plane {
+ struct drm_plane plane;
+ struct drm_property *alpha_property;
+ struct drm_property *blend_mode_property;
+ struct drm_property *fbc_hsize_r_property;
+ struct drm_property *fbc_hsize_y_property;
+ struct drm_property *fbc_hsize_uv_property;
+ struct drm_property *y2r_coef_property;
+ struct drm_property *pallete_en_property;
+ struct drm_property *pallete_color_property;
+ u32 index;
+};
+
+struct sprd_plane_state {
+ struct drm_plane_state state;
+ u8 alpha;
+ u8 blend_mode;
+ u32 fbc_hsize_r;
+ u32 fbc_hsize_y;
+ u32 fbc_hsize_uv;
+ u32 y2r_coef;
+ u32 pallete_en;
+ u32 pallete_color;
+};
+
+LIST_HEAD(dpu_core_head);
+LIST_HEAD(dpu_clk_head);
+LIST_HEAD(dpu_glb_head);
+
+static int sprd_dpu_init(struct sprd_dpu *dpu);
+static int sprd_dpu_uninit(struct sprd_dpu *dpu);
+
+static inline struct sprd_plane *to_sprd_plane(struct drm_plane *plane)
+{
+ return container_of(plane, struct sprd_plane, plane);
+}
+
+static inline struct
+sprd_plane_state *to_sprd_plane_state(const struct drm_plane_state *state)
+{
+ return container_of(state, struct sprd_plane_state, state);
+}
+
+static int sprd_dpu_iommu_map(struct device *dev,
+ struct sprd_gem_obj *sprd_gem)
+{
+ /*
+ * TODO:
+ * Currently only supports dumb buffer,
+ * will support iommu in the future.
+ */
+
+ return 0;
+}
+
+static void sprd_dpu_iommu_unmap(struct device *dev,
+ struct sprd_gem_obj *sprd_gem)
+{
+ /*
+ * TODO:
+ * Currently only supports dumb buffer,
+ * will support iommu in the future.
+ */
+}
+
+static int of_get_logo_memory_info(struct sprd_dpu *dpu,
+ struct device_node *np)
+{
+ struct device_node *node;
+ struct resource r;
+ int ret;
+ struct dpu_context *ctx = &dpu->ctx;
+
+ node = of_parse_phandle(np, "sprd,logo-memory", 0);
+ if (!node) {
+ DRM_INFO("no sprd,logo-memory specified\n");
+ return 0;
+ }
+
+ ret = of_address_to_resource(node, 0, &r);
+ of_node_put(node);
+ if (ret) {
+ DRM_ERROR("invalid logo reserved memory node!\n");
+ return -EINVAL;
+ }
+
+ ctx->logo_addr = r.start;
+ ctx->logo_size = resource_size(&r);
+
+ return 0;
+}
+
+static int sprd_plane_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *new_state)
+{
+ struct drm_plane_state *curr_state = plane->state;
+ struct drm_framebuffer *fb;
+ struct drm_gem_object *obj;
+ struct sprd_gem_obj *sprd_gem;
+ struct sprd_dpu *dpu;
+ int i;
+
+ if ((curr_state->fb == new_state->fb) || !new_state->fb)
+ return 0;
+
+ fb = new_state->fb;
+ dpu = crtc_to_dpu(new_state->crtc);
+
+ if (!dpu->ctx.is_inited) {
+ DRM_WARN("dpu has already powered off\n");
+ return 0;
+ }
+
+ for (i = 0; i < fb->format->num_planes; i++) {
+ obj = drm_gem_fb_get_obj(fb, i);
+ sprd_gem = to_sprd_gem_obj(obj);
+ if (sprd_gem->sgtb && sprd_gem->sgtb->nents > 1)
+ sprd_dpu_iommu_map(&dpu->dev, sprd_gem);
+ }
+
+ return 0;
+}
+
+static void sprd_plane_cleanup_fb(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_plane_state *curr_state = plane->state;
+ struct drm_framebuffer *fb;
+ struct drm_gem_object *obj;
+ struct sprd_gem_obj *sprd_gem;
+ struct sprd_dpu *dpu;
+ int i;
+ static atomic_t logo2animation = { -1 };
+
+ if ((curr_state->fb == old_state->fb) || !old_state->fb)
+ return;
+
+ fb = old_state->fb;
+ dpu = crtc_to_dpu(old_state->crtc);
+
+ if (!dpu->ctx.is_inited) {
+ DRM_WARN("dpu has already powered off\n");
+ return;
+ }
+
+ for (i = 0; i < fb->format->num_planes; i++) {
+ obj = drm_gem_fb_get_obj(fb, i);
+ sprd_gem = to_sprd_gem_obj(obj);
+ if (sprd_gem->sgtb && sprd_gem->sgtb->nents > 1)
+ sprd_dpu_iommu_unmap(&dpu->dev, sprd_gem);
+ }
+
+ if (unlikely(atomic_inc_not_zero(&logo2animation)) &&
+ dpu->ctx.logo_addr) {
+ DRM_INFO("free logo memory addr:0x%lx size:0x%lx\n",
+ dpu->ctx.logo_addr, dpu->ctx.logo_size);
+ free_reserved_area(phys_to_virt(dpu->ctx.logo_addr),
+ phys_to_virt(dpu->ctx.logo_addr + dpu->ctx.logo_size),
+ -1, "logo");
+ }
+}
+
+static int sprd_plane_atomic_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ DRM_DEBUG("%s()\n", __func__);
+
+ return 0;
+}
+
+static void sprd_plane_atomic_update(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_plane_state *state = plane->state;
+ struct drm_framebuffer *fb = plane->state->fb;
+ struct drm_gem_object *obj;
+ struct sprd_gem_obj *sprd_gem;
+ struct sprd_plane *p = to_sprd_plane(plane);
+ struct sprd_plane_state *s = to_sprd_plane_state(state);
+ struct sprd_dpu *dpu = crtc_to_dpu(plane->state->crtc);
+ struct sprd_dpu_layer *layer = &dpu->layers[p->index];
+ int i;
+
+ if (plane->state->crtc->state->active_changed) {
+ DRM_DEBUG("resume or suspend, no need to update plane\n");
+ return;
+ }
+
+ if (s->pallete_en) {
+ layer->index = p->index;
+ layer->dst_x = state->crtc_x;
+ layer->dst_y = state->crtc_y;
+ layer->dst_w = state->crtc_w;
+ layer->dst_h = state->crtc_h;
+ layer->alpha = s->alpha;
+ layer->blending = s->blend_mode;
+ layer->pallete_en = s->pallete_en;
+ layer->pallete_color = s->pallete_color;
+ dpu->pending_planes++;
+ DRM_DEBUG("%s() pallete_color = %u, index = %u\n",
+ __func__, layer->pallete_color, layer->index);
+ return;
+ }
+
+ layer->index = p->index;
+ layer->src_x = state->src_x >> 16;
+ layer->src_y = state->src_y >> 16;
+ layer->src_w = state->src_w >> 16;
+ layer->src_h = state->src_h >> 16;
+ layer->dst_x = state->crtc_x;
+ layer->dst_y = state->crtc_y;
+ layer->dst_w = state->crtc_w;
+ layer->dst_h = state->crtc_h;
+ layer->rotation = state->rotation;
+ layer->planes = fb->format->num_planes;
+ layer->format = fb->format->format;
+ layer->alpha = s->alpha;
+ layer->blending = s->blend_mode;
+ layer->xfbc = fb->modifier;
+ layer->header_size_r = s->fbc_hsize_r;
+ layer->header_size_y = s->fbc_hsize_y;
+ layer->header_size_uv = s->fbc_hsize_uv;
+ layer->y2r_coef = s->y2r_coef;
+ layer->pallete_en = s->pallete_en;
+ layer->pallete_color = s->pallete_color;
+
+ DRM_DEBUG("%s() alpha = %u, blending = %u, rotation = %u, y2r_coef = %u\n",
+ __func__, layer->alpha, layer->blending, layer->rotation, s->y2r_coef);
+
+ DRM_DEBUG("%s() xfbc = %u, hsize_r = %u, hsize_y = %u, hsize_uv = %u\n",
+ __func__, layer->xfbc, layer->header_size_r,
+ layer->header_size_y, layer->header_size_uv);
+
+ for (i = 0; i < layer->planes; i++) {
+ obj = drm_gem_fb_get_obj(fb, i);
+ sprd_gem = to_sprd_gem_obj(obj);
+ layer->addr[i] = sprd_gem->dma_addr + fb->offsets[i];
+ layer->pitch[i] = fb->pitches[i];
+ }
+
+ dpu->pending_planes++;
+}
+
+static void sprd_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct sprd_plane *p = to_sprd_plane(plane);
+
+ /*
+ * NOTE:
+ * The dpu->core->flip() will disable all the planes each time.
+ * So there is no need to impliment the atomic_disable() function.
+ * But this function can not be removed, because it will change
+ * to call atomic_update() callback instead. Which will cause
+ * kernel panic in sprd_plane_atomic_update().
+ *
+ * We do nothing here but just print a debug log.
+ */
+ DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
+}
+
+static void sprd_plane_reset(struct drm_plane *plane)
+{
+ struct sprd_plane *p = to_sprd_plane(plane);
+ struct sprd_plane_state *s;
+
+ DRM_INFO("%s()\n", __func__);
+
+ if (plane->state) {
+ __drm_atomic_helper_plane_destroy_state(plane->state);
+
+ s = to_sprd_plane_state(plane->state);
+ memset(s, 0, sizeof(*s));
+ } else {
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return;
+ plane->state = &s->state;
+ }
+
+ s->state.plane = plane;
+ s->state.zpos = p->index;
+ s->alpha = 255;
+ s->blend_mode = DRM_MODE_BLEND_PIXEL_NONE;
+}
+
+static struct drm_plane_state *
+sprd_plane_atomic_duplicate_state(struct drm_plane *plane)
+{
+ struct sprd_plane_state *s;
+ struct sprd_plane_state *old_state = to_sprd_plane_state(plane->state);
+
+ DRM_DEBUG("%s()\n", __func__);
+
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return NULL;
+
+ __drm_atomic_helper_plane_duplicate_state(plane, &s->state);
+
+ WARN_ON(s->state.plane != plane);
+
+ s->alpha = old_state->alpha;
+ s->blend_mode = old_state->blend_mode;
+ s->fbc_hsize_r = old_state->fbc_hsize_r;
+ s->fbc_hsize_y = old_state->fbc_hsize_y;
+ s->fbc_hsize_uv = old_state->fbc_hsize_uv;
+ s->y2r_coef = old_state->y2r_coef;
+ s->pallete_en = old_state->pallete_en;
+ s->pallete_color = old_state->pallete_color;
+
+ return &s->state;
+}
+
+static void sprd_plane_atomic_destroy_state(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ DRM_DEBUG("%s()\n", __func__);
+
+ __drm_atomic_helper_plane_destroy_state(state);
+ kfree(to_sprd_plane_state(state));
+}
+
+static int sprd_plane_atomic_set_property(struct drm_plane *plane,
+ struct drm_plane_state *state,
+ struct drm_property *property,
+ u64 val)
+{
+ struct sprd_plane *p = to_sprd_plane(plane);
+ struct sprd_plane_state *s = to_sprd_plane_state(state);
+
+ DRM_DEBUG("%s() name = %s, val = %llu\n",
+ __func__, property->name, val);
+
+ if (property == p->alpha_property)
+ s->alpha = val;
+ else if (property == p->blend_mode_property)
+ s->blend_mode = val;
+ else if (property == p->fbc_hsize_r_property)
+ s->fbc_hsize_r = val;
+ else if (property == p->fbc_hsize_y_property)
+ s->fbc_hsize_y = val;
+ else if (property == p->fbc_hsize_uv_property)
+ s->fbc_hsize_uv = val;
+ else if (property == p->y2r_coef_property)
+ s->y2r_coef = val;
+ else if (property == p->pallete_en_property)
+ s->pallete_en = val;
+ else if (property == p->pallete_color_property)
+ s->pallete_color = val;
+ else {
+ DRM_ERROR("property %s is invalid\n", property->name);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sprd_plane_atomic_get_property(struct drm_plane *plane,
+ const struct drm_plane_state *state,
+ struct drm_property *property,
+ u64 *val)
+{
+ struct sprd_plane *p = to_sprd_plane(plane);
+ const struct sprd_plane_state *s = to_sprd_plane_state(state);
+
+ DRM_DEBUG("%s() name = %s\n", __func__, property->name);
+
+ if (property == p->alpha_property)
+ *val = s->alpha;
+ else if (property == p->blend_mode_property)
+ *val = s->blend_mode;
+ else if (property == p->fbc_hsize_r_property)
+ *val = s->fbc_hsize_r;
+ else if (property == p->fbc_hsize_y_property)
+ *val = s->fbc_hsize_y;
+ else if (property == p->fbc_hsize_uv_property)
+ *val = s->fbc_hsize_uv;
+ else if (property == p->y2r_coef_property)
+ *val = s->y2r_coef;
+ else if (property == p->pallete_en_property)
+ *val = s->pallete_en;
+ else if (property == p->pallete_color_property)
+ *val = s->pallete_color;
+ else {
+ DRM_ERROR("property %s is invalid\n", property->name);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sprd_plane_create_properties(struct sprd_plane *p, int index)
+{
+ struct drm_property *prop;
+ static const struct drm_prop_enum_list blend_mode_enum_list[] = {
+ { DRM_MODE_BLEND_PIXEL_NONE, "None" },
+ { DRM_MODE_BLEND_PREMULTI, "Pre-multiplied" },
+ { DRM_MODE_BLEND_COVERAGE, "Coverage" },
+ };
+
+ /* create rotation property */
+ drm_plane_create_rotation_property(&p->plane,
+ DRM_MODE_ROTATE_0,
+ DRM_MODE_ROTATE_MASK |
+ DRM_MODE_REFLECT_MASK);
+
+ /* create zpos property */
+ drm_plane_create_zpos_immutable_property(&p->plane, index);
+
+ /* create layer alpha property */
+ prop = drm_property_create_range(p->plane.dev, 0, "alpha", 0, 255);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 255);
+ p->alpha_property = prop;
+
+ /* create blend mode property */
+ prop = drm_property_create_enum(p->plane.dev, DRM_MODE_PROP_ENUM,
+ "pixel blend mode",
+ blend_mode_enum_list,
+ ARRAY_SIZE(blend_mode_enum_list));
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop,
+ DRM_MODE_BLEND_PIXEL_NONE);
+ p->blend_mode_property = prop;
+
+ /* create fbc header size property */
+ prop = drm_property_create_range(p->plane.dev, 0,
+ "FBC header size RGB", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 0);
+ p->fbc_hsize_r_property = prop;
+
+ prop = drm_property_create_range(p->plane.dev, 0,
+ "FBC header size Y", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 0);
+ p->fbc_hsize_y_property = prop;
+
+ prop = drm_property_create_range(p->plane.dev, 0,
+ "FBC header size UV", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 0);
+ p->fbc_hsize_uv_property = prop;
+
+ /* create y2r coef property */
+ prop = drm_property_create_range(p->plane.dev, 0,
+ "YUV2RGB coef", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 0);
+ p->y2r_coef_property = prop;
+
+ /* create pallete enable property */
+ prop = drm_property_create_range(p->plane.dev, 0,
+ "pallete enable", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 0);
+ p->pallete_en_property = prop;
+
+ /* create pallete color property */
+ prop = drm_property_create_range(p->plane.dev, 0,
+ "pallete color", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ drm_object_attach_property(&p->plane.base, prop, 0);
+ p->pallete_color_property = prop;
+
+ return 0;
+}
+
+static const struct drm_plane_helper_funcs sprd_plane_helper_funcs = {
+ .prepare_fb = sprd_plane_prepare_fb,
+ .cleanup_fb = sprd_plane_cleanup_fb,
+ .atomic_check = sprd_plane_atomic_check,
+ .atomic_update = sprd_plane_atomic_update,
+ .atomic_disable = sprd_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs sprd_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = sprd_plane_reset,
+ .atomic_duplicate_state = sprd_plane_atomic_duplicate_state,
+ .atomic_destroy_state = sprd_plane_atomic_destroy_state,
+ .atomic_set_property = sprd_plane_atomic_set_property,
+ .atomic_get_property = sprd_plane_atomic_get_property,
+};
+
+static struct drm_plane *sprd_plane_init(struct drm_device *drm,
+ struct sprd_dpu *dpu)
+{
+ struct drm_plane *primary = NULL;
+ struct sprd_plane *p = NULL;
+ struct dpu_capability cap = {};
+ int err, i;
+
+ if (dpu->core && dpu->core->capability)
+ dpu->core->capability(&dpu->ctx, &cap);
+
+ dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
+ sizeof(struct sprd_dpu_layer), GFP_KERNEL);
+ if (!dpu->layers)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < cap.max_layers; i++) {
+
+ p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return ERR_PTR(-ENOMEM);
+
+ err = drm_universal_plane_init(drm, &p->plane, 1,
+ &sprd_plane_funcs, cap.fmts_ptr,
+ cap.fmts_cnt, NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (err) {
+ DRM_ERROR("fail to init primary plane\n");
+ return ERR_PTR(err);
+ }
+
+ drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
+
+ sprd_plane_create_properties(p, i);
+
+ p->index = i;
+ if (i == 0)
+ primary = &p->plane;
+ }
+
+ if (p)
+ DRM_INFO("dpu plane init ok\n");
+
+ return primary;
+}
+
+static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+
+ DRM_INFO("%s() set mode: %s\n", __func__, dpu->mode->name);
+
+ /*
+ * TODO:
+ * Currently, low simulator resolution only support
+ * DPI mode, support for EDPI in the future.
+ */
+ if (mode->type & DRM_MODE_TYPE_BUILTIN) {
+ dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
+ return;
+ }
+
+ if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
+ (dpu->mode->vdisplay == dpu->mode->vtotal))
+ dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
+ else
+ dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
+
+ if (dpu->core && dpu->core->modeset) {
+ if (crtc->state->mode_changed) {
+ struct drm_mode_modeinfo umode;
+
+ drm_mode_convert_to_umode(&umode, mode);
+ dpu->core->modeset(&dpu->ctx, &umode);
+ }
+ }
+}
+
+static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+ DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
+
+ if (mode->type & DRM_MODE_TYPE_DEFAULT)
+ dpu->mode = (struct drm_display_mode *)mode;
+
+ if (mode->type & DRM_MODE_TYPE_PREFERRED) {
+ dpu->mode = (struct drm_display_mode *)mode;
+ drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
+ }
+
+ if (mode->type & DRM_MODE_TYPE_BUILTIN)
+ dpu->mode = (struct drm_display_mode *)mode;
+
+ return MODE_OK;
+}
+
+static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+ static bool is_enabled = true;
+
+ DRM_INFO("%s()\n", __func__);
+
+ /*
+ * add if condition to avoid resume dpu for SR feature.
+ */
+ if (crtc->state->mode_changed && !crtc->state->active_changed)
+ return;
+
+ if (is_enabled)
+ is_enabled = false;
+ else
+ pm_runtime_get_sync(dpu->dev.parent);
+
+ sprd_dpu_init(dpu);
+
+ enable_irq(dpu->ctx.irq);
+}
+
+static void sprd_crtc_wait_last_commit_complete(struct drm_crtc *crtc)
+{
+ struct drm_crtc_commit *commit;
+ int ret, i = 0;
+
+ spin_lock(&crtc->commit_lock);
+ list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
+ i++;
+ /* skip the first entry, that's the current commit */
+ if (i == 2)
+ break;
+ }
+ if (i == 2)
+ drm_crtc_commit_get(commit);
+ spin_unlock(&crtc->commit_lock);
+
+ if (i != 2)
+ return;
+
+ ret = wait_for_completion_interruptible_timeout(&commit->cleanup_done,
+ HZ);
+ if (ret == 0)
+ DRM_WARN("wait last commit completion timed out\n");
+
+ drm_crtc_commit_put(commit);
+}
+
+static void sprd_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+ struct drm_device *drm = dpu->crtc.dev;
+
+ DRM_INFO("%s()\n", __func__);
+
+ /* add if condition to avoid suspend dpu for SR feature */
+ if (crtc->state->mode_changed && !crtc->state->active_changed)
+ return;
+
+ sprd_crtc_wait_last_commit_complete(crtc);
+
+ disable_irq(dpu->ctx.irq);
+
+ sprd_dpu_uninit(dpu);
+
+ pm_runtime_put(dpu->dev.parent);
+
+ spin_lock_irq(&drm->event_lock);
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&drm->event_lock);
+}
+
+static int sprd_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ DRM_DEBUG("%s()\n", __func__);
+
+ return 0;
+}
+
+static void sprd_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+ DRM_DEBUG("%s()\n", __func__);
+
+ down(&dpu->ctx.refresh_lock);
+
+ memset(dpu->layers, 0, sizeof(*dpu->layers) * dpu->pending_planes);
+
+ dpu->pending_planes = 0;
+}
+
+static void sprd_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+ struct drm_device *drm = dpu->crtc.dev;
+
+ DRM_DEBUG("%s()\n", __func__);
+
+ if (dpu->core && dpu->core->flip &&
+ dpu->pending_planes && !dpu->ctx.disable_flip)
+ dpu->core->flip(&dpu->ctx, dpu->layers, dpu->pending_planes);
+
+ up(&dpu->ctx.refresh_lock);
+
+ spin_lock_irq(&drm->event_lock);
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&drm->event_lock);
+}
+
+static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+ DRM_DEBUG("%s()\n", __func__);
+
+ if (dpu->core && dpu->core->enable_vsync)
+ dpu->core->enable_vsync(&dpu->ctx);
+
+ return 0;
+}
+
+static void sprd_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+ DRM_DEBUG("%s()\n", __func__);
+
+ if (dpu->core && dpu->core->disable_vsync)
+ dpu->core->disable_vsync(&dpu->ctx);
+}
+
+static int sprd_crtc_create_properties(struct drm_crtc *crtc)
+{
+ struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+ struct drm_device *drm = dpu->crtc.dev;
+ struct drm_property *prop;
+ struct drm_property_blob *blob;
+ size_t blob_size;
+
+ blob_size = strlen(dpu->ctx.version) + 1;
+
+ blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
+ dpu->ctx.version);
+ if (IS_ERR(blob)) {
+ DRM_ERROR("drm_property_create_blob dpu version failed\n");
+ return PTR_ERR(blob);
+ }
+
+ /* create dpu version property */
+ prop = drm_property_create(drm,
+ DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
+ "dpu version", 0);
+ if (!prop) {
+ DRM_ERROR("drm_property_create dpu version failed\n");
+ return -ENOMEM;
+ }
+ drm_object_attach_property(&crtc->base, prop, blob->base.id);
+
+ /* create corner size property */
+ prop = drm_property_create(drm,
+ DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_RANGE,
+ "corner size", 0);
+ if (!prop) {
+ DRM_ERROR("drm_property_create corner size failed\n");
+ return -ENOMEM;
+ }
+ drm_object_attach_property(&crtc->base, prop, dpu->ctx.corner_size);
+
+ return 0;
+}
+
+static const struct drm_crtc_helper_funcs sprd_crtc_helper_funcs = {
+ .mode_set_nofb = sprd_crtc_mode_set_nofb,
+ .mode_valid = sprd_crtc_mode_valid,
+ .atomic_check = sprd_crtc_atomic_check,
+ .atomic_begin = sprd_crtc_atomic_begin,
+ .atomic_flush = sprd_crtc_atomic_flush,
+ .atomic_enable = sprd_crtc_atomic_enable,
+ .atomic_disable = sprd_crtc_atomic_disable,
+};
+
+static const struct drm_crtc_funcs sprd_crtc_funcs = {
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .enable_vblank = sprd_crtc_enable_vblank,
+ .disable_vblank = sprd_crtc_disable_vblank,
+};
+
+static int sprd_crtc_init(struct drm_device *drm, struct drm_crtc *crtc,
+ struct drm_plane *primary)
+{
+ struct device_node *port;
+ int err;
+
+ /*
+ * set crtc port so that drm_of_find_possible_crtcs call works
+ */
+ port = of_parse_phandle(drm->dev->of_node, "ports", 0);
+ if (!port) {
+ DRM_ERROR("find 'ports' phandle of %s failed\n",
+ drm->dev->of_node->full_name);
+ return -EINVAL;
+ }
+ of_node_put(port);
+ crtc->port = port;
+
+ err = drm_crtc_init_with_planes(drm, crtc, primary, NULL,
+ &sprd_crtc_funcs, NULL);
+ if (err) {
+ DRM_ERROR("failed to init crtc.\n");
+ return err;
+ }
+
+ drm_mode_crtc_set_gamma_size(crtc, 256);
+
+ drm_crtc_helper_add(crtc, &sprd_crtc_helper_funcs);
+
+ sprd_crtc_create_properties(crtc);
+
+ DRM_INFO("%s() ok\n", __func__);
+ return 0;
+}
+
+int sprd_dpu_run(struct sprd_dpu *dpu)
+{
+ struct dpu_context *ctx = &dpu->ctx;
+
+ down(&ctx->refresh_lock);
+
+ if (!ctx->is_inited) {
+ DRM_ERROR("dpu is not initialized\n");
+ up(&ctx->refresh_lock);
+ return -EINVAL;
+ }
+
+ if (!ctx->is_stopped) {
+ up(&ctx->refresh_lock);
+ return 0;
+ }
+
+ if (dpu->core && dpu->core->run)
+ dpu->core->run(ctx);
+
+ up(&ctx->refresh_lock);
+
+ drm_crtc_vblank_on(&dpu->crtc);
+
+ return 0;
+}
+
+int sprd_dpu_stop(struct sprd_dpu *dpu)
+{
+ struct dpu_context *ctx = &dpu->ctx;
+
+ down(&ctx->refresh_lock);
+
+ if (!ctx->is_inited) {
+ DRM_ERROR("dpu is not initialized\n");
+ up(&ctx->refresh_lock);
+ return -EINVAL;
+ }
+
+ if (ctx->is_stopped) {
+ up(&ctx->refresh_lock);
+ return 0;
+ }
+
+ if (dpu->core && dpu->core->stop)
+ dpu->core->stop(ctx);
+
+ up(&ctx->refresh_lock);
+
+ drm_crtc_handle_vblank(&dpu->crtc);
+ drm_crtc_vblank_off(&dpu->crtc);
+
+ return 0;
+}
+
+static int sprd_dpu_init(struct sprd_dpu *dpu)
+{
+ struct dpu_context *ctx = &dpu->ctx;
+
+ down(&ctx->refresh_lock);
+
+ if (dpu->ctx.is_inited) {
+ up(&ctx->refresh_lock);
+ return 0;
+ }
+
+ if (dpu->glb && dpu->glb->power)
+ dpu->glb->power(ctx, true);
+ if (dpu->glb && dpu->glb->enable)
+ dpu->glb->enable(ctx);
+
+ if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
+ dpu->glb->reset(ctx);
+
+ if (dpu->clk && dpu->clk->init)
+ dpu->clk->init(ctx);
+ if (dpu->clk && dpu->clk->enable)
+ dpu->clk->enable(ctx);
+
+ if (dpu->core && dpu->core->init)
+ dpu->core->init(ctx);
+ if (dpu->core && dpu->core->ifconfig)
+ dpu->core->ifconfig(ctx);
+
+ ctx->is_inited = true;
+
+ up(&ctx->refresh_lock);
+
+ return 0;
+}
+
+static int sprd_dpu_uninit(struct sprd_dpu *dpu)
+{
+ struct dpu_context *ctx = &dpu->ctx;
+
+ down(&ctx->refresh_lock);
+
+ if (!dpu->ctx.is_inited) {
+ up(&ctx->refresh_lock);
+ return 0;
+ }
+
+ if (dpu->core && dpu->core->uninit)
+ dpu->core->uninit(ctx);
+ if (dpu->clk && dpu->clk->disable)
+ dpu->clk->disable(ctx);
+ if (dpu->glb && dpu->glb->disable)
+ dpu->glb->disable(ctx);
+ if (dpu->glb && dpu->glb->power)
+ dpu->glb->power(ctx, false);
+
+ ctx->is_inited = false;
+
+ up(&ctx->refresh_lock);
+
+ return 0;
+}
+
+static irqreturn_t sprd_dpu_isr(int irq, void *data)
+{
+ struct sprd_dpu *dpu = data;
+ struct dpu_context *ctx = &dpu->ctx;
+ u32 int_mask = 0;
+
+ if (dpu->core && dpu->core->isr)
+ int_mask = dpu->core->isr(ctx);
+
+ if (int_mask & DISPC_INT_TE_MASK) {
+ if (ctx->te_check_en) {
+ ctx->evt_te = true;
+ wake_up_interruptible_all(&ctx->te_wq);
+ }
+ }
+
+ if (int_mask & DISPC_INT_ERR_MASK)
+ DRM_WARN("Warning: dpu underflow!\n");
+
+ if ((int_mask & DISPC_INT_DPI_VSYNC_MASK) && ctx->is_inited)
+ drm_crtc_handle_vblank(&dpu->crtc);
+
+ return IRQ_HANDLED;
+}
+
+static int sprd_dpu_irq_request(struct sprd_dpu *dpu)
+{
+ int err;
+ int irq_num;
+
+ irq_num = irq_of_parse_and_map(dpu->dev.of_node, 0);
+ if (!irq_num) {
+ DRM_ERROR("error: dpu parse irq num failed\n");
+ return -EINVAL;
+ }
+ DRM_INFO("dpu irq_num = %d\n", irq_num);
+
+ irq_set_status_flags(irq_num, IRQ_NOAUTOEN);
+ err = devm_request_irq(&dpu->dev, irq_num, sprd_dpu_isr,
+ 0, "DISPC", dpu);
+ if (err) {
+ DRM_ERROR("error: dpu request irq failed\n");
+ return -EINVAL;
+ }
+ dpu->ctx.irq = irq_num;
+ dpu->ctx.dpu_isr = sprd_dpu_isr;
+
+ return 0;
+}
+
+static int sprd_dpu_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct sprd_drm *sprd = drm->dev_private;
+ struct sprd_dpu *dpu = dev_get_drvdata(dev);
+ struct drm_plane *plane;
+ int err;
+
+ DRM_INFO("%s()\n", __func__);
+
+ plane = sprd_plane_init(drm, dpu);
+ if (IS_ERR_OR_NULL(plane)) {
+ err = PTR_ERR(plane);
+ return err;
+ }
+
+ err = sprd_crtc_init(drm, &dpu->crtc, plane);
+ if (err)
+ return err;
+
+ sprd_dpu_irq_request(dpu);
+
+ sprd->dpu_dev = dev;
+
+ return 0;
+}
+
+static void sprd_dpu_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct sprd_dpu *dpu = dev_get_drvdata(dev);
+
+ DRM_INFO("%s()\n", __func__);
+
+ drm_crtc_cleanup(&dpu->crtc);
+}
+
+static const struct component_ops dpu_component_ops = {
+ .bind = sprd_dpu_bind,
+ .unbind = sprd_dpu_unbind,
+};
+
+static int sprd_dpu_context_init(struct sprd_dpu *dpu,
+ struct device_node *np)
+{
+ u32 temp;
+ struct resource r;
+ struct dpu_context *ctx = &dpu->ctx;
+
+ if (dpu->core && dpu->core->parse_dt)
+ dpu->core->parse_dt(&dpu->ctx, np);
+ if (dpu->clk && dpu->clk->parse_dt)
+ dpu->clk->parse_dt(&dpu->ctx, np);
+ if (dpu->glb && dpu->glb->parse_dt)
+ dpu->glb->parse_dt(&dpu->ctx, np);
+
+ if (!of_property_read_u32(np, "sprd,dev-id", &temp))
+ ctx->id = temp;
+
+ if (of_address_to_resource(np, 0, &r)) {
+ DRM_ERROR("parse dt base address failed\n");
+ return -ENODEV;
+ }
+ ctx->base = (unsigned long)ioremap_nocache(r.start,
+ resource_size(&r));
+ if (ctx->base == 0) {
+ DRM_ERROR("ioremap base address failed\n");
+ return -EFAULT;
+ }
+
+ of_get_logo_memory_info(dpu, np);
+
+ sema_init(&ctx->refresh_lock, 1);
+
+ return 0;
+}
+
+static int sprd_dpu_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct sprd_dpu *dpu;
+ int ret;
+
+ dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
+ if (!dpu)
+ return -ENOMEM;
+
+ ret = sprd_dpu_context_init(dpu, np);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, dpu);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ return component_add(&pdev->dev, &dpu_component_ops);
+}
+
+static int sprd_dpu_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &dpu_component_ops);
+ return 0;
+}
+
+static const struct of_device_id dpu_match_table[] = {
+ { .compatible = "sprd,display-processor",},
+ {},
+};
+
+static struct platform_driver sprd_dpu_driver = {
+ .probe = sprd_dpu_probe,
+ .remove = sprd_dpu_remove,
+ .driver = {
+ .name = "sprd-dpu-drv",
+ .of_match_table = dpu_match_table,
+ },
+};
+module_platform_driver(sprd_dpu_driver);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("Unisoc Display Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_dpu.h b/drivers/gpu/drm/sprd/sprd_dpu.h
new file mode 100644
index 0000000..998ebc7
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dpu.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef __SPRD_DPU_H__
+#define __SPRD_DPU_H__
+
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <video/videomode.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <uapi/drm/drm_mode.h>
+#include "disp_lib.h"
+
+#define DISPC_INT_DONE_MASK BIT(0)
+#define DISPC_INT_TE_MASK BIT(1)
+#define DISPC_INT_ERR_MASK BIT(2)
+#define DISPC_INT_EDPI_TE_MASK BIT(3)
+#define DISPC_INT_UPDATE_DONE_MASK BIT(4)
+#define DISPC_INT_DPI_VSYNC_MASK BIT(5)
+#define DISPC_INT_WB_DONE_MASK BIT(6)
+#define DISPC_INT_WB_FAIL_MASK BIT(7)
+
+/* NOTE: this mask is not a realy dpu interrupt mask */
+#define DISPC_INT_FENCE_SIGNAL_REQUEST BIT(31)
+
+enum {
+ SPRD_DISPC_IF_DBI = 0,
+ SPRD_DISPC_IF_DPI,
+ SPRD_DISPC_IF_EDPI,
+ SPRD_DISPC_IF_LIMIT
+};
+
+enum {
+ SPRD_IMG_DATA_ENDIAN_B0B1B2B3 = 0,
+ SPRD_IMG_DATA_ENDIAN_B3B2B1B0,
+ SPRD_IMG_DATA_ENDIAN_B2B3B0B1,
+ SPRD_IMG_DATA_ENDIAN_B1B0B3B2,
+ SPRD_IMG_DATA_ENDIAN_LIMIT
+};
+
+enum {
+ DISPC_CLK_ID_CORE = 0,
+ DISPC_CLK_ID_DBI,
+ DISPC_CLK_ID_DPI,
+ DISPC_CLK_ID_MAX
+};
+
+enum {
+ ENHANCE_CFG_ID_ENABLE,
+ ENHANCE_CFG_ID_DISABLE,
+ ENHANCE_CFG_ID_SCL,
+ ENHANCE_CFG_ID_EPF,
+ ENHANCE_CFG_ID_HSV,
+ ENHANCE_CFG_ID_CM,
+ ENHANCE_CFG_ID_SLP,
+ ENHANCE_CFG_ID_GAMMA,
+ ENHANCE_CFG_ID_LTM,
+ ENHANCE_CFG_ID_CABC,
+ ENHANCE_CFG_ID_SLP_LUT,
+ ENHANCE_CFG_ID_LUT3D,
+ ENHANCE_CFG_ID_MAX
+};
+
+struct sprd_dpu_layer {
+ u8 index;
+ u8 planes;
+ u32 addr[4];
+ u32 pitch[4];
+ s16 src_x;
+ s16 src_y;
+ s16 src_w;
+ s16 src_h;
+ s16 dst_x;
+ s16 dst_y;
+ u16 dst_w;
+ u16 dst_h;
+ u32 format;
+ u32 alpha;
+ u32 blending;
+ u32 rotation;
+ u32 xfbc;
+ u32 height;
+ u32 header_size_r;
+ u32 header_size_y;
+ u32 header_size_uv;
+ u32 y2r_coef;
+ u8 pallete_en;
+ u32 pallete_color;
+};
+
+struct dpu_capability {
+ u32 max_layers;
+ const u32 *fmts_ptr;
+ u32 fmts_cnt;
+};
+
+struct dpu_context;
+
+struct dpu_core_ops {
+ int (*parse_dt)(struct dpu_context *ctx,
+ struct device_node *np);
+ u32 (*version)(struct dpu_context *ctx);
+ int (*init)(struct dpu_context *ctx);
+ void (*uninit)(struct dpu_context *ctx);
+ void (*run)(struct dpu_context *ctx);
+ void (*stop)(struct dpu_context *ctx);
+ void (*disable_vsync)(struct dpu_context *ctx);
+ void (*enable_vsync)(struct dpu_context *ctx);
+ u32 (*isr)(struct dpu_context *ctx);
+ void (*ifconfig)(struct dpu_context *ctx);
+ void (*write_back)(struct dpu_context *ctx, u8 count, bool debug);
+ void (*flip)(struct dpu_context *ctx,
+ struct sprd_dpu_layer layers[], u8 count);
+ int (*capability)(struct dpu_context *ctx,
+ struct dpu_capability *cap);
+ void (*bg_color)(struct dpu_context *ctx, u32 color);
+ void (*enhance_set)(struct dpu_context *ctx, u32 id, void *param);
+ void (*enhance_get)(struct dpu_context *ctx, u32 id, void *param);
+ int (*modeset)(struct dpu_context *ctx,
+ struct drm_mode_modeinfo *mode);
+ bool (*check_raw_int)(struct dpu_context *ctx, u32 mask);
+};
+
+struct dpu_clk_ops {
+ int (*parse_dt)(struct dpu_context *ctx,
+ struct device_node *np);
+ int (*init)(struct dpu_context *ctx);
+ int (*uinit)(struct dpu_context *ctx);
+ int (*enable)(struct dpu_context *ctx);
+ int (*disable)(struct dpu_context *ctx);
+ int (*update)(struct dpu_context *ctx, int clk_id, int val);
+};
+
+struct dpu_glb_ops {
+ int (*parse_dt)(struct dpu_context *ctx,
+ struct device_node *np);
+ void (*enable)(struct dpu_context *ctx);
+ void (*disable)(struct dpu_context *ctx);
+ void (*reset)(struct dpu_context *ctx);
+ void (*power)(struct dpu_context *ctx, int enable);
+};
+
+struct dpu_context {
+ unsigned long base;
+ u32 base_offset[2];
+ const char *version;
+ u32 corner_size;
+ int irq;
+ u8 if_type;
+ u8 id;
+ bool is_inited;
+ bool is_stopped;
+ bool disable_flip;
+ struct videomode vm;
+ struct semaphore refresh_lock;
+ struct work_struct wb_work;
+ struct tasklet_struct dvfs_task;
+ u32 wb_addr_p;
+ irqreturn_t (*dpu_isr)(int irq, void *data);
+ wait_queue_head_t te_wq;
+ bool te_check_en;
+ bool evt_te;
+ unsigned long logo_addr;
+ unsigned long logo_size;
+ struct work_struct cabc_work;
+ struct work_struct cabc_bl_update;
+};
+
+struct sprd_dpu {
+ struct device dev;
+ struct drm_crtc crtc;
+ struct dpu_context ctx;
+ struct dpu_core_ops *core;
+ struct dpu_clk_ops *clk;
+ struct dpu_glb_ops *glb;
+ struct drm_display_mode *mode;
+ struct sprd_dpu_layer *layers;
+ u8 pending_planes;
+};
+
+extern struct list_head dpu_core_head;
+extern struct list_head dpu_clk_head;
+extern struct list_head dpu_glb_head;
+
+static inline struct sprd_dpu *crtc_to_dpu(struct drm_crtc *crtc)
+{
+ return crtc ? container_of(crtc, struct sprd_dpu, crtc) : NULL;
+}
+
+#define dpu_core_ops_register(entry) \
+ disp_ops_register(entry, &dpu_core_head)
+#define dpu_clk_ops_register(entry) \
+ disp_ops_register(entry, &dpu_clk_head)
+#define dpu_glb_ops_register(entry) \
+ disp_ops_register(entry, &dpu_glb_head)
+
+#define dpu_core_ops_attach(str) \
+ disp_ops_attach(str, &dpu_core_head)
+#define dpu_clk_ops_attach(str) \
+ disp_ops_attach(str, &dpu_clk_head)
+#define dpu_glb_ops_attach(str) \
+ disp_ops_attach(str, &dpu_glb_head)
+
+int sprd_dpu_run(struct sprd_dpu *dpu);
+int sprd_dpu_stop(struct sprd_dpu *dpu);
+
+#endif
--
2.7.4

2019-12-10 08:38:24

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 5/8] dt-bindings: display: add Unisoc's mipi dsi&dphy bindings

From: Kevin Tang <[email protected]>

Adds MIPI DSI Master and MIPI DSI-PHY (D-PHY)
support for Unisoc's display subsystem.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
.../devicetree/bindings/display/sprd/dphy.txt | 49 ++++++++++++++++
.../devicetree/bindings/display/sprd/dsi.txt | 68 ++++++++++++++++++++++
2 files changed, 117 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sprd/dphy.txt
create mode 100644 Documentation/devicetree/bindings/display/sprd/dsi.txt

diff --git a/Documentation/devicetree/bindings/display/sprd/dphy.txt b/Documentation/devicetree/bindings/display/sprd/dphy.txt
new file mode 100644
index 0000000..474c2b2
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sprd/dphy.txt
@@ -0,0 +1,49 @@
+Unisoc MIPI DSI-PHY (D-PHY)
+============================================================================
+
+Required properties:
+ - compatible: value should be "sprd,dsi-phy".
+ - reg: must be the dsi controller base address.
+ - #address-cells, #size-cells: should be set respectively to <1> and <0>
+
+Video interfaces:
+ Device node can contain video interface port nodes according to [1].
+ The following are properties specific to those nodes:
+
+ port node inbound:
+ - reg: (required) must be 0.
+ port node outbound:
+ - reg: (required) must be 1.
+
+ endpoint node connected from DSI controller node (reg = 0):
+ - remote-endpoint: specifies the endpoint in DSI node.
+ endpoint node connected to panel node (reg = 1):
+ - remote-endpoint: specifies the endpoint in panel node.
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+
+Example:
+
+ dphy: dphy {
+ compatible = "sprd,dsi-phy";
+ reg = <0x0 0x63100000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* input port*/
+ port@1 {
+ reg = <1>;
+ dphy_in: endpoint {
+ remote-endpoint = <&dsi_out>;
+ };
+ };
+
+ /* output port */
+ port@0 {
+ reg = <0>;
+ dphy_out: endpoint {
+ remote-endpoint = <&panel_in>;
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/display/sprd/dsi.txt b/Documentation/devicetree/bindings/display/sprd/dsi.txt
new file mode 100644
index 0000000..1719ff5
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sprd/dsi.txt
@@ -0,0 +1,68 @@
+Unisoc MIPI DSI Master
+=============================================================================
+
+Required properties:
+ - compatible: value should be "sprd,dsi-host";
+ - reg: physical base address and length of the registers set for the device
+ - interrupts: should contain DSI interrupt
+ - clocks: list of clock specifiers, must contain an entry for each required
+ entry in clock-names
+ - clock-names: list of clock names sorted in the same order as the clocks
+ property.
+ - #address-cells, #size-cells: should be set respectively to <1> and <0>
+ according to DSI host bindings (see MIPI DSI bindings [1])
+
+Optional properties:
+ - power-domains: a phandle to DSIM power domain node
+
+Child nodes:
+ Should contain DSI peripheral nodes (see MIPI DSI bindings [1]).
+
+Video interfaces:
+ Device node can contain video interface port nodes according to [2].
+ The following are properties specific to those nodes:
+
+ port node inbound:
+ - reg: (required) must be 0.
+ port node outbound:
+ - reg: (required) must be 1.
+
+ endpoint node connected from DPU node (reg = 0):
+ - remote-endpoint: specifies the endpoint in DPU node.
+ endpoint node connected to D-PHY node (reg = 1):
+ - remote-endpoint: specifies the endpoint in D-PHY node.
+
+[1]: Documentation/devicetree/bindings/display/mipi-dsi-bus.txt
+[2]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+SoC specific DT entry:
+
+ dsi: dsi@63100000 {
+ compatible = "sprd,dsi-host";
+ reg = <0 0x63100000 0 0x1000>;
+ interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "clk_aon_apb_disp_eb";
+ clocks = <&clk_aon_top_gates 2>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dsi_in: endpoint {
+ remote-endpoint = <&dpu_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dsi_out: endpoint@1 {
+ remote-endpoint = <&dphy_in>;
+ };
+ };
+ };
+ };
\ No newline at end of file
--
2.7.4

2019-12-10 08:38:32

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 6/8] drm/sprd: add Unisoc's drm mipi dsi&dphy driver

From: Kevin Tang <[email protected]>

Adds dsi host controller support for the Unisoc's display subsystem.
Adds dsi phy support for the Unisoc's display subsystem.
Only MIPI DSI Displays supported, DP/TV/HMDI will be support
in the feature.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
drivers/gpu/drm/sprd/Makefile | 8 +-
drivers/gpu/drm/sprd/dphy/Makefile | 7 +
drivers/gpu/drm/sprd/dphy/pll/Makefile | 3 +
drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c | 640 ++++++++++
drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c | 254 ++++
drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h | 329 +++++
drivers/gpu/drm/sprd/dsi/Makefile | 7 +
drivers/gpu/drm/sprd/dsi/core/Makefile | 3 +
drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c | 1186 +++++++++++++++++
drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h | 1417 +++++++++++++++++++++
drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c | 392 ++++++
drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c | 544 ++++++++
drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h | 28 +
drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h | 1102 ++++++++++++++++
drivers/gpu/drm/sprd/sprd_dphy.c | 235 ++++
drivers/gpu/drm/sprd/sprd_dphy.h | 121 ++
drivers/gpu/drm/sprd/sprd_dsi.c | 722 +++++++++++
drivers/gpu/drm/sprd/sprd_dsi.h | 210 +++
18 files changed, 7206 insertions(+), 2 deletions(-)
create mode 100644 drivers/gpu/drm/sprd/dphy/Makefile
create mode 100644 drivers/gpu/drm/sprd/dphy/pll/Makefile
create mode 100644 drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c
create mode 100644 drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c
create mode 100644 drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h
create mode 100644 drivers/gpu/drm/sprd/dsi/Makefile
create mode 100644 drivers/gpu/drm/sprd/dsi/core/Makefile
create mode 100644 drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c
create mode 100644 drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h
create mode 100644 drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c
create mode 100644 drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c
create mode 100644 drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h
create mode 100644 drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h
create mode 100644 drivers/gpu/drm/sprd/sprd_dphy.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dphy.h
create mode 100644 drivers/gpu/drm/sprd/sprd_dsi.c
create mode 100644 drivers/gpu/drm/sprd/sprd_dsi.h

diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
index 3f188ab..78d3ddb 100644
--- a/drivers/gpu/drm/sprd/Makefile
+++ b/drivers/gpu/drm/sprd/Makefile
@@ -6,7 +6,11 @@ subdir-ccflags-y += -I$(src)

obj-y := sprd_drm.o \
sprd_gem.o \
- sprd_dpu.o
+ sprd_dpu.o \
+ sprd_dsi.o \
+ sprd_dphy.o

obj-y += disp_lib.o
-obj-y += dpu/
\ No newline at end of file
+obj-y += dpu/
+obj-y += dsi/
+obj-y += dphy/
diff --git a/drivers/gpu/drm/sprd/dphy/Makefile b/drivers/gpu/drm/sprd/dphy/Makefile
new file mode 100644
index 0000000..8a1012d6
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dphy/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+subdir-ccflags-y += -I$(src)
+
+obj-y := sprd_dphy_api.o
+
+obj-y += pll/
diff --git a/drivers/gpu/drm/sprd/dphy/pll/Makefile b/drivers/gpu/drm/sprd/dphy/pll/Makefile
new file mode 100644
index 0000000..1fe9638
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dphy/pll/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += megacores_sharkle.o
\ No newline at end of file
diff --git a/drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c b/drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c
new file mode 100644
index 0000000..d365dec
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c
@@ -0,0 +1,640 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <asm/div64.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+
+#include "sprd_dphy.h"
+
+#define L 0
+#define H 1
+#define CLK 0
+#define DATA 1
+#define INFINITY 0xffffffff
+#define MIN_OUTPUT_FREQ (100)
+
+#define AVERAGE(a, b) (min(a, b) + abs((b) - (a)) / 2)
+
+enum TIMING {
+ NONE,
+ REQUEST_TIME,
+ PREPARE_TIME,
+ SETTLE_TIME,
+ ZERO_TIME,
+ TRAIL_TIME,
+ EXIT_TIME,
+ CLKPOST_TIME,
+ TA_GET,
+ TA_GO,
+ TA_SURE,
+ TA_WAIT,
+};
+
+struct pll_regs {
+ union __reg_03__ {
+ struct __03 {
+ u8 prbs_bist: 1;
+ u8 en_lp_treot: 1;
+ u8 lpf_sel: 4;
+ u8 txfifo_bypass: 1;
+ u8 freq_hopping: 1;
+ } bits;
+ u8 val;
+ } _03;
+ union __reg_04__ {
+ struct __04 {
+ u8 div: 3;
+ u8 masterof8lane: 1;
+ u8 hop_trig: 1;
+ u8 cp_s: 2;
+ u8 fdk_s: 1;
+ } bits;
+ u8 val;
+ } _04;
+ union __reg_06__ {
+ struct __06 {
+ u8 nint: 7;
+ u8 mod_en: 1;
+ } bits;
+ u8 val;
+ } _06;
+ union __reg_07__ {
+ struct __07 {
+ u8 kdelta_h: 8;
+ } bits;
+ u8 val;
+ } _07;
+ union __reg_08__ {
+ struct __08 {
+ u8 vco_band: 1;
+ u8 sdm_en: 1;
+ u8 refin: 2;
+ u8 kdelta_l: 4;
+ } bits;
+ u8 val;
+ } _08;
+ union __reg_09__ {
+ struct __09 {
+ u8 kint_h: 8;
+ } bits;
+ u8 val;
+ } _09;
+ union __reg_0a__ {
+ struct __0a {
+ u8 kint_m: 8;
+ } bits;
+ u8 val;
+ } _0a;
+ union __reg_0b__ {
+ struct __0b {
+ u8 out_sel: 4;
+ u8 kint_l: 4;
+ } bits;
+ u8 val;
+ } _0b;
+ union __reg_0c__ {
+ struct __0c {
+ u8 kstep_h: 8;
+ } bits;
+ u8 val;
+ } _0c;
+ union __reg_0d__ {
+ struct __0d {
+ u8 kstep_m: 8;
+ } bits;
+ u8 val;
+ } _0d;
+ union __reg_0e__ {
+ struct __0e {
+ u8 pll_pu_byp: 1;
+ u8 pll_pu: 1;
+ u8 hsbist_len: 2;
+ u8 stopstate_sel: 1;
+ u8 kstep_l: 3;
+ } bits;
+ u8 val;
+ } _0e;
+
+ union __reg_0f__ {
+ struct __0f {
+ u8 det_delay:2;
+ /*12 - 15 -> bit2 - bit5*/
+ u8 kdelta: 4;
+ u8 ldo0p4:2;
+ } bits;
+ u8 val;
+ } _0f;
+
+};
+
+struct dphy_pll {
+ u8 refin; /* Pre-divider control signal */
+ u8 cp_s; /* 00: SDM_EN=1, 10: SDM_EN=0 */
+ u8 fdk_s; /* PLL mode control: integer or fraction */
+ u8 hop_en;
+ u8 mod_en; /* ssc enable */
+ u8 sdm_en;
+ u8 div;
+ u8 int_n; /* integer N PLL */
+ u32 ref_clk; /* dphy reference clock, unit: MHz */
+ u32 freq; /* panel config, unit: KHz */
+ int hop_delta;
+ u32 hop_period;
+ u32 fvco; /* MHz */
+ u32 potential_fvco; /* MHz */
+ u32 nint; /* sigma delta modulator NINT control */
+ u32 kint; /* sigma delta modulator KINT control */
+ u32 sign;
+ u32 kdelta;
+ u32 kstep;
+ u8 lpf_sel; /* low pass filter control */
+ u8 out_sel; /* post divider control */
+ u8 vco_band; /* vco range */
+ u8 det_delay;
+};
+
+static struct pll_regs regs;
+static struct dphy_pll pll;
+
+/* sharkle */
+#define VCO_BAND_LOW 750
+#define VCO_BAND_MID 1100
+#define VCO_BAND_HIGH 1500
+#define PHY_REF_CLK 26000
+
+static int dphy_calc_pll_param(struct dphy_pll *pll)
+{
+ int i;
+ const u32 khz = 1000;
+ const u32 mhz = 1000000;
+ const unsigned long long factor = 100;
+ unsigned long long tmp;
+ u8 delta;
+
+ if (!pll || !pll->freq)
+ goto FAIL;
+
+ pll->potential_fvco = pll->freq / khz; /* MHz */
+ pll->ref_clk = PHY_REF_CLK / khz; /* MHz */
+
+ for (i = 0; i < 4; ++i) {
+ if (pll->potential_fvco >= VCO_BAND_LOW &&
+ pll->potential_fvco <= VCO_BAND_HIGH) {
+ pll->fvco = pll->potential_fvco;
+ pll->out_sel = BIT(i);
+ break;
+ }
+ pll->potential_fvco <<= 1;
+ }
+ if (pll->fvco == 0)
+ goto FAIL;
+
+ if (pll->fvco >= VCO_BAND_LOW && pll->fvco <= VCO_BAND_MID) {
+ /* vco band control */
+ pll->vco_band = 0x0;
+ /* low pass filter control */
+ pll->lpf_sel = 1;
+ } else if (pll->fvco > VCO_BAND_MID && pll->fvco <= VCO_BAND_HIGH) {
+ pll->vco_band = 0x1;
+ pll->lpf_sel = 0;
+ } else
+ goto FAIL;
+
+ pll->nint = pll->fvco / pll->ref_clk;
+ tmp = pll->fvco * factor * mhz;
+ do_div(tmp, pll->ref_clk);
+ tmp = tmp - pll->nint * factor * mhz;
+ tmp *= BIT(20);
+ do_div(tmp, 100000000);
+ pll->kint = (u32)tmp;
+ pll->refin = 3; /* pre-divider bypass */
+ pll->sdm_en = true; /* use fraction N PLL */
+ pll->fdk_s = 0x1; /* fraction */
+ pll->cp_s = 0x0;
+ pll->det_delay = 0x1;
+
+ if (pll->hop_en) {
+ if ((pll->hop_delta == 0) || (pll->hop_period == 0))
+ return 0;
+
+ if (pll->hop_delta < 0) {
+ delta = -pll->hop_delta;
+ pll->sign = true;
+ } else {
+ delta = pll->hop_delta;
+ pll->sign = false;
+ }
+ delta = delta * (i + 1);
+ pll->kstep = pll->ref_clk * pll->hop_period;
+ pll->kdelta = (1 << 20) * delta / pll->ref_clk /
+ pll->kstep + pll->sign * (1 << 11);
+ }
+
+ if (pll->mod_en) {
+ delta = pll->freq / khz * 2 * (i + 1) * 15 / 1000;
+ pll->kstep = pll->ref_clk * pll->hop_period;
+ pll->kdelta = (((1 << 20) * delta / pll->ref_clk /
+ pll->kstep));
+ }
+
+ return 0;
+
+FAIL:
+ if (pll)
+ pll->fvco = 0;
+
+ pr_err("failed to calculate dphy pll parameters\n");
+
+ return -1;
+}
+
+static int dphy_set_pll_reg(struct regmap *regmap, struct dphy_pll *pll)
+{
+ int i;
+ u8 *val;
+
+ u8 regs_addr[] = {
+ 0x03, 0x04, 0x06, 0x07, 0x08, 0x09,
+ 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+ };
+
+ if (!pll || !pll->fvco)
+ goto FAIL;
+
+ regs._03.bits.prbs_bist = 1;
+ regs._03.bits.en_lp_treot = true;
+ regs._03.bits.lpf_sel = pll->lpf_sel;
+ regs._03.bits.txfifo_bypass = 0;
+ regs._03.bits.freq_hopping = pll->hop_en;
+ regs._04.bits.div = pll->div;
+ regs._04.bits.masterof8lane = 1;
+ regs._04.bits.cp_s = pll->cp_s;
+ regs._04.bits.fdk_s = pll->fdk_s;
+ regs._06.bits.nint = pll->nint;
+ regs._06.bits.mod_en = pll->mod_en;
+ regs._07.bits.kdelta_h = pll->kdelta >> 4;
+ regs._07.bits.kdelta_h |= pll->sign << 7;
+ regs._08.bits.vco_band = pll->vco_band;
+ regs._08.bits.sdm_en = pll->sdm_en;
+ regs._08.bits.refin = pll->refin;
+ regs._08.bits.kdelta_l = pll->kdelta & 0xf;
+ regs._09.bits.kint_h = pll->kint >> 12;
+ regs._0a.bits.kint_m = (pll->kint >> 4) & 0xff;
+ regs._0b.bits.out_sel = pll->out_sel;
+ regs._0b.bits.kint_l = pll->kint & 0xf;
+ regs._0c.bits.kstep_h = pll->kstep >> 11;
+ regs._0d.bits.kstep_m = (pll->kstep >> 3) & 0xff;
+ regs._0e.bits.pll_pu_byp = 0;
+ regs._0e.bits.pll_pu = 0;
+ regs._0e.bits.stopstate_sel = 1;
+ regs._0e.bits.kstep_l = pll->kstep & 0x7;
+ regs._0f.bits.det_delay = pll->det_delay;
+ regs._0f.bits.kdelta = pll->kdelta >> 12;
+
+ val = (u8 *)&regs;
+
+ for (i = 0; i < sizeof(regs_addr); ++i) {
+ regmap_write(regmap, regs_addr[i], val[i]);
+ pr_debug("%02x: %02x\n", regs_addr[i], val[i]);
+ }
+
+ return 0;
+
+FAIL:
+ pr_err("failed to set dphy pll registers\n");
+
+ return -1;
+}
+
+static int dphy_pll_config(struct dphy_context *ctx)
+{
+ int ret;
+ struct regmap *regmap = ctx->regmap;
+
+ pll.freq = ctx->freq;
+
+ /* FREQ = 26M * (NINT + KINT / 2^20) / out_sel */
+ ret = dphy_calc_pll_param(&pll);
+ if (ret)
+ goto FAIL;
+ ret = dphy_set_pll_reg(regmap, &pll);
+ if (ret)
+ goto FAIL;
+
+ return 0;
+
+FAIL:
+ pr_err("failed to config dphy pll\n");
+
+ return -1;
+}
+
+static int dphy_set_timing_regs(struct regmap *regmap, int type, u8 val[])
+{
+ switch (type) {
+ case REQUEST_TIME:
+ regmap_write(regmap, 0x31, val[CLK]);
+ regmap_write(regmap, 0x41, val[DATA]);
+ regmap_write(regmap, 0x51, val[DATA]);
+ regmap_write(regmap, 0x61, val[DATA]);
+ regmap_write(regmap, 0x71, val[DATA]);
+
+ regmap_write(regmap, 0x90, val[CLK]);
+ regmap_write(regmap, 0xa0, val[DATA]);
+ regmap_write(regmap, 0xb0, val[DATA]);
+ regmap_write(regmap, 0xc0, val[DATA]);
+ regmap_write(regmap, 0xd0, val[DATA]);
+ break;
+ case PREPARE_TIME:
+ regmap_write(regmap, 0x32, val[CLK]);
+ regmap_write(regmap, 0x42, val[DATA]);
+ regmap_write(regmap, 0x52, val[DATA]);
+ regmap_write(regmap, 0x62, val[DATA]);
+ regmap_write(regmap, 0x72, val[DATA]);
+
+ regmap_write(regmap, 0x91, val[CLK]);
+ regmap_write(regmap, 0xa1, val[DATA]);
+ regmap_write(regmap, 0xb1, val[DATA]);
+ regmap_write(regmap, 0xc1, val[DATA]);
+ regmap_write(regmap, 0xd1, val[DATA]);
+ break;
+ case ZERO_TIME:
+ regmap_write(regmap, 0x33, val[CLK]);
+ regmap_write(regmap, 0x43, val[DATA]);
+ regmap_write(regmap, 0x53, val[DATA]);
+ regmap_write(regmap, 0x63, val[DATA]);
+ regmap_write(regmap, 0x73, val[DATA]);
+
+ regmap_write(regmap, 0x92, val[CLK]);
+ regmap_write(regmap, 0xa2, val[DATA]);
+ regmap_write(regmap, 0xb2, val[DATA]);
+ regmap_write(regmap, 0xc2, val[DATA]);
+ regmap_write(regmap, 0xd2, val[DATA]);
+ break;
+ case TRAIL_TIME:
+ regmap_write(regmap, 0x34, val[CLK]);
+ regmap_write(regmap, 0x44, val[DATA]);
+ regmap_write(regmap, 0x54, val[DATA]);
+ regmap_write(regmap, 0x64, val[DATA]);
+ regmap_write(regmap, 0x74, val[DATA]);
+
+ regmap_write(regmap, 0x93, val[CLK]);
+ regmap_write(regmap, 0xa3, val[DATA]);
+ regmap_write(regmap, 0xb3, val[DATA]);
+ regmap_write(regmap, 0xc3, val[DATA]);
+ regmap_write(regmap, 0xd3, val[DATA]);
+ break;
+ case EXIT_TIME:
+ regmap_write(regmap, 0x36, val[CLK]);
+ regmap_write(regmap, 0x46, val[DATA]);
+ regmap_write(regmap, 0x56, val[DATA]);
+ regmap_write(regmap, 0x66, val[DATA]);
+ regmap_write(regmap, 0x76, val[DATA]);
+
+ regmap_write(regmap, 0x95, val[CLK]);
+ regmap_write(regmap, 0xA5, val[DATA]);
+ regmap_write(regmap, 0xB5, val[DATA]);
+ regmap_write(regmap, 0xc5, val[DATA]);
+ regmap_write(regmap, 0xd5, val[DATA]);
+ break;
+ case CLKPOST_TIME:
+ regmap_write(regmap, 0x35, val[CLK]);
+ regmap_write(regmap, 0x94, val[CLK]);
+ break;
+
+ /* the following just use default value */
+ case SETTLE_TIME:
+ case TA_GET:
+ case TA_GO:
+ case TA_SURE:
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int dphy_timing_config(struct dphy_context *ctx)
+{
+ u8 val[2];
+ u32 tmp = 0;
+ u32 range[2], constant;
+ u32 t_ui, t_byteck, t_half_byteck;
+ const u32 factor = 2;
+ const u32 scale = 100;
+ struct regmap *regmap = ctx->regmap;
+
+ /* t_ui: 1 ui, byteck: 8 ui, half byteck: 4 ui */
+ t_ui = 1000 * scale / (ctx->freq / 1000);
+ t_byteck = t_ui << 3;
+ t_half_byteck = t_ui << 2;
+ constant = t_ui << 1;
+
+ /* REQUEST_TIME: HS T-LPX: LP-01
+ * For T-LPX, mipi spec defined min value is 50ns,
+ * but maybe it shouldn't be too small, because BTA,
+ * LP-10, LP-00, LP-01, all of this is related to T-LPX.
+ */
+ range[L] = 50 * scale;
+ range[H] = INFINITY;
+ val[CLK] = DIV_ROUND_UP(range[L] * (factor << 1), t_byteck) - 2;
+ val[DATA] = val[CLK];
+ dphy_set_timing_regs(regmap, REQUEST_TIME, val);
+
+ /* PREPARE_TIME: HS sequence: LP-00 */
+ range[L] = 38 * scale;
+ range[H] = 95 * scale;
+ tmp = AVERAGE(range[L], range[H]);
+ val[CLK] = DIV_ROUND_UP(AVERAGE(range[L], range[H]),
+ t_half_byteck) - 1;
+ range[L] = 40 * scale + 4 * t_ui;
+ range[H] = 85 * scale + 6 * t_ui;
+ tmp |= AVERAGE(range[L], range[H]) << 16;
+ val[DATA] = DIV_ROUND_UP(AVERAGE(range[L], range[H]),
+ t_half_byteck) - 1;
+ dphy_set_timing_regs(regmap, PREPARE_TIME, val);
+
+ /* ZERO_TIME: HS-ZERO */
+ range[L] = 300 * scale;
+ range[H] = INFINITY;
+ val[CLK] = DIV_ROUND_UP(range[L] * factor + (tmp & 0xffff)
+ - 525 * t_byteck / 100, t_byteck) - 2;
+ range[L] = 145 * scale + 10 * t_ui;
+ val[DATA] = DIV_ROUND_UP(range[L] * factor
+ + ((tmp >> 16) & 0xffff) - 525 * t_byteck / 100,
+ t_byteck) - 2;
+ dphy_set_timing_regs(regmap, ZERO_TIME, val);
+
+ /* TRAIL_TIME: HS-TRAIL */
+ range[L] = 60 * scale;
+ range[H] = INFINITY;
+ val[CLK] = DIV_ROUND_UP(range[L] * factor - constant, t_half_byteck);
+ range[L] = max(8 * t_ui, 60 * scale + 4 * t_ui);
+ val[DATA] = DIV_ROUND_UP(range[L] * 3 / 2 - constant, t_half_byteck) - 2;
+ dphy_set_timing_regs(regmap, TRAIL_TIME, val);
+
+ /* EXIT_TIME: */
+ range[L] = 100 * scale;
+ range[H] = INFINITY;
+ val[CLK] = DIV_ROUND_UP(range[L] * factor, t_byteck) - 2;
+ val[DATA] = val[CLK];
+ dphy_set_timing_regs(regmap, EXIT_TIME, val);
+
+ /* CLKPOST_TIME: */
+ range[L] = 60 * scale + 52 * t_ui;
+ range[H] = INFINITY;
+ val[CLK] = DIV_ROUND_UP(range[L] * factor, t_byteck) - 2;
+ val[DATA] = val[CLK];
+ dphy_set_timing_regs(regmap, CLKPOST_TIME, val);
+
+ /* SETTLE_TIME:
+ * This time is used for receiver. So for transmitter,
+ * it can be ignored.
+ */
+
+ /* TA_GO:
+ * transmitter drives bridge state(LP-00) before releasing control,
+ * reg 0x1f default value: 0x04, which is good.
+ */
+
+ /* TA_SURE:
+ * After LP-10 state and before bridge state(LP-00),
+ * reg 0x20 default value: 0x01, which is good.
+ */
+
+ /* TA_GET:
+ * receiver drives Bridge state(LP-00) before releasing control
+ * reg 0x21 default value: 0x03, which is good.
+ */
+
+ return 0;
+}
+
+static int dphy_hop_start(struct dphy_context *ctx)
+{
+ struct regmap *regmap = ctx->regmap;
+
+ /* start hopping */
+ regs._04.bits.hop_trig = !regs._04.bits.hop_trig;
+ regmap_write(regmap, 0x04, regs._04.val);
+
+ mdelay(1);
+
+ pr_info("start hopping\n");
+ return 0;
+}
+
+static int dphy_hop_stop(struct dphy_context *ctx)
+{
+ struct regmap *regmap = ctx->regmap;
+
+ regs._03.bits.freq_hopping = 0;
+ regmap_write(regmap, 0x03, regs._03.val);
+
+ pr_info("stop hopping\n");
+ return 0;
+}
+
+static int dphy_hop_config(struct dphy_context *ctx, int delta, int period)
+{
+ dphy_hop_stop(ctx);
+
+ if (delta == 0) {
+ pll.hop_en = false;
+ return 0;
+ }
+
+ pll.hop_en = true;
+ pll.mod_en = false;
+ pll.hop_delta = delta / 1000; /* Mhz */
+ pll.hop_period = period; /* us */
+
+ dphy_pll_config(ctx);
+ dphy_hop_start(ctx);
+
+ return 0;
+}
+
+static int dphy_ssc_start(struct dphy_context *ctx)
+{
+ pll.mod_en = true;
+ pll.hop_en = false;
+ pll.hop_period = 15;/* us */
+
+ dphy_pll_config(ctx);
+
+ mdelay(1);
+
+ return 0;
+}
+
+static int dphy_ssc_stop(struct dphy_context *ctx)
+{
+ struct regmap *regmap = ctx->regmap;
+
+ pll.mod_en = false;
+ regs._06.bits.mod_en = false;
+ regmap_write(regmap, 0x06, regs._06.val);
+
+ return 0;
+}
+
+static int dphy_ssc_en(struct dphy_context *ctx, bool en)
+{
+ if (en) {
+ pr_info("ssc enable\n");
+ dphy_ssc_start(ctx);
+ } else {
+ dphy_ssc_stop(ctx);
+ pr_info("ssc disable\n");
+ }
+
+ return 0;
+}
+
+/**
+ * Force D-PHY PLL to stay on while in ULPS
+ * @param phy: pointer to structure
+ * which holds information about the d-phy module
+ * @param force (1) disable (0)
+ * @note To follow the programming model, use wakeup_pll function
+ */
+static void dphy_force_pll(struct dphy_context *ctx, int force)
+{
+ u8 data;
+ struct regmap *regmap = ctx->regmap;
+
+ if (force)
+ data = 0x03;
+ else
+ data = 0x0;
+
+ /* for megocores, to force pll, dphy register should be set */
+ regmap_write(regmap, 0x0e, data);
+}
+
+static struct dphy_pll_ops megacores_sharkle_ops = {
+ .pll_config = dphy_pll_config,
+ .timing_config = dphy_timing_config,
+ .hop_config = dphy_hop_config,
+ .ssc_en = dphy_ssc_en,
+ .force_pll = dphy_force_pll,
+};
+
+static struct ops_entry entry = {
+ .ver = "sprd,megacores-sharkle",
+ .ops = &megacores_sharkle_ops,
+};
+
+static int __init sprd_dphy_pll_register(void)
+{
+ return dphy_pll_ops_register(&entry);
+}
+
+subsys_initcall(sprd_dphy_pll_register);
diff --git a/drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c b/drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c
new file mode 100644
index 0000000..96c02d5
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "sprd_dphy_hal.h"
+
+static int dphy_wait_pll_locked(struct sprd_dphy *dphy)
+{
+ u32 i = 0;
+
+ for (i = 0; i < 50000; i++) {
+ if (dphy_hal_is_pll_locked(dphy))
+ return 0;
+ udelay(3);
+ }
+
+ pr_err("error: dphy pll can not be locked\n");
+ return -1;
+}
+
+static int dphy_wait_datalane_stop_state(struct sprd_dphy *dphy, u8 mask)
+{
+ u32 i = 0;
+
+ for (i = 0; i < 5000; i++) {
+ if (dphy_hal_is_stop_state_datalane(dphy) == mask)
+ return 0;
+ udelay(10);
+ }
+
+ pr_err("wait datalane stop-state time out\n");
+ return -1;
+}
+
+static int dphy_wait_datalane_ulps_active(struct sprd_dphy *dphy, u8 mask)
+{
+ u32 i = 0;
+
+ for (i = 0; i < 5000; i++) {
+ if (dphy_hal_is_ulps_active_datalane(dphy) == mask)
+ return 0;
+ udelay(10);
+ }
+
+ pr_err("wait datalane ulps-active time out\n");
+ return -1;
+}
+
+static int dphy_wait_clklane_stop_state(struct sprd_dphy *dphy)
+{
+ u32 i = 0;
+
+ for (i = 0; i < 5000; i++) {
+ if (dphy_hal_is_stop_state_clklane(dphy))
+ return 0;
+ udelay(10);
+ }
+
+ pr_err("wait clklane stop-state time out\n");
+ return -1;
+}
+
+static int dphy_wait_clklane_ulps_active(struct sprd_dphy *dphy)
+{
+ u32 i = 0;
+
+ for (i = 0; i < 5000; i++) {
+ if (dphy_hal_is_ulps_active_clklane(dphy))
+ return 0;
+ udelay(10);
+ }
+
+ pr_err("wait clklane ulps-active time out\n");
+ return -1;
+}
+
+int sprd_dphy_configure(struct sprd_dphy *dphy)
+{
+ struct dphy_pll_ops *pll = dphy->pll;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ pr_info("lanes : %d\n", ctx->lanes);
+ pr_info("freq : %d\n", ctx->freq);
+
+ dphy_hal_rstz(dphy, 0);
+ dphy_hal_shutdownz(dphy, 0);
+ dphy_hal_clklane_en(dphy, 0);
+
+ dphy_hal_test_clr(dphy, 0);
+ dphy_hal_test_clr(dphy, 1);
+ dphy_hal_test_clr(dphy, 0);
+
+ pll->pll_config(ctx);
+ pll->timing_config(ctx);
+
+ dphy_hal_shutdownz(dphy, 1);
+ dphy_hal_rstz(dphy, 1);
+ dphy_hal_stop_wait_time(dphy, 0x1C);
+ dphy_hal_clklane_en(dphy, 1);
+ dphy_hal_datalane_en(dphy);
+
+ if (dphy_wait_pll_locked(dphy))
+ return -1;
+
+ return 0;
+}
+
+void sprd_dphy_reset(struct sprd_dphy *dphy)
+{
+ dphy_hal_rstz(dphy, 0);
+ udelay(10);
+ dphy_hal_rstz(dphy, 1);
+}
+
+void sprd_dphy_shutdown(struct sprd_dphy *dphy)
+{
+ dphy_hal_shutdownz(dphy, 0);
+ udelay(10);
+ dphy_hal_shutdownz(dphy, 1);
+}
+
+int sprd_dphy_hop_config(struct sprd_dphy *dphy, int delta, int period)
+{
+ struct dphy_pll_ops *pll = dphy->pll;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (pll->hop_config)
+ return pll->hop_config(ctx, delta, period);
+
+ return 0;
+}
+
+int sprd_dphy_ssc_en(struct sprd_dphy *dphy, bool en)
+{
+ struct dphy_pll_ops *pll = dphy->pll;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (pll->ssc_en)
+ return pll->ssc_en(ctx, en);
+
+ return 0;
+}
+
+int sprd_dphy_close(struct sprd_dphy *dphy)
+{
+ if (!dphy)
+ return -1;
+
+ dphy_hal_rstz(dphy, 0);
+ dphy_hal_shutdownz(dphy, 0);
+ dphy_hal_rstz(dphy, 1);
+
+ return 0;
+}
+
+int sprd_dphy_data_ulps_enter(struct sprd_dphy *dphy)
+{
+ u8 lane_mask = (1 << dphy->ctx.lanes) - 1;
+
+ dphy_hal_datalane_ulps_rqst(dphy, 1);
+
+ dphy_wait_datalane_ulps_active(dphy, lane_mask);
+
+ dphy_hal_datalane_ulps_rqst(dphy, 0);
+
+ return 0;
+}
+
+int sprd_dphy_data_ulps_exit(struct sprd_dphy *dphy)
+{
+ u8 lane_mask = (1 << dphy->ctx.lanes) - 1;
+
+ dphy_hal_datalane_ulps_exit(dphy, 1);
+
+ dphy_wait_datalane_stop_state(dphy, lane_mask);
+
+ dphy_hal_datalane_ulps_exit(dphy, 0);
+
+ return 0;
+}
+
+int sprd_dphy_clk_ulps_enter(struct sprd_dphy *dphy)
+{
+ dphy_hal_clklane_ulps_rqst(dphy, 1);
+
+ dphy_wait_clklane_ulps_active(dphy);
+
+ dphy_hal_clklane_ulps_rqst(dphy, 0);
+
+ return 0;
+}
+
+int sprd_dphy_clk_ulps_exit(struct sprd_dphy *dphy)
+{
+ dphy_hal_clklane_ulps_exit(dphy, 1);
+
+ dphy_wait_clklane_stop_state(dphy);
+
+ dphy_hal_clklane_ulps_exit(dphy, 0);
+
+ return 0;
+}
+
+void sprd_dphy_force_pll(struct sprd_dphy *dphy, bool enable)
+{
+ dphy_hal_force_pll(dphy, enable);
+}
+
+void sprd_dphy_hs_clk_en(struct sprd_dphy *dphy, bool enable)
+{
+ dphy_hal_clk_hs_rqst(dphy, enable);
+
+ dphy_wait_pll_locked(dphy);
+}
+
+void sprd_dphy_test_write(struct sprd_dphy *dphy, u8 address, u8 data)
+{
+ dphy_hal_test_en(dphy, 1);
+
+ dphy_hal_test_din(dphy, address);
+
+ dphy_hal_test_clk(dphy, 1);
+ dphy_hal_test_clk(dphy, 0);
+
+ dphy_hal_test_en(dphy, 0);
+
+ dphy_hal_test_din(dphy, data);
+
+ dphy_hal_test_clk(dphy, 1);
+ dphy_hal_test_clk(dphy, 0);
+}
+
+u8 sprd_dphy_test_read(struct sprd_dphy *dphy, u8 address)
+{
+ dphy_hal_test_en(dphy, 1);
+
+ dphy_hal_test_din(dphy, address);
+
+ dphy_hal_test_clk(dphy, 1);
+ dphy_hal_test_clk(dphy, 0);
+
+ dphy_hal_test_en(dphy, 0);
+
+ udelay(1);
+
+ return dphy_hal_test_dout(dphy);
+}
+
+
+
diff --git a/drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h b/drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h
new file mode 100644
index 0000000..b1d32be
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h
@@ -0,0 +1,329 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include "sprd_dphy.h"
+
+
+/*
+ * Reset D-PHY module
+ * @param dphy: pointer to structure
+ * which holds information about the d-dphy module
+ * @param reset
+ */
+static inline void dphy_hal_rstz(struct sprd_dphy *dphy, int level)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->rstz)
+ ppi->rstz(ctx, level);
+}
+
+/*
+ * Power up/down D-PHY module
+ * @param dphy: pointer to structure
+ * which holds information about the d-dphy module
+ * @param enable (1: shutdown)
+ */
+static inline void dphy_hal_shutdownz(struct sprd_dphy *dphy, int level)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->shutdownz)
+ ppi->shutdownz(ctx, level);
+}
+
+/*
+ * Force D-PHY PLL to stay on while in ULPS
+ * @param dphy: pointer to structure
+ * which holds information about the d-dphy module
+ * @param force (1) disable (0)
+ * @note To follow the programming model, use wakeup_pll function
+ */
+static inline void dphy_hal_force_pll(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->force_pll)
+ ppi->force_pll(ctx, en);
+}
+
+static inline void dphy_hal_clklane_ulps_rqst(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->clklane_ulps_rqst)
+ ppi->clklane_ulps_rqst(ctx, en);
+}
+
+static inline void dphy_hal_clklane_ulps_exit(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->clklane_ulps_exit)
+ ppi->clklane_ulps_exit(ctx, en);
+}
+
+static inline void dphy_hal_datalane_ulps_rqst(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->datalane_ulps_rqst)
+ ppi->datalane_ulps_rqst(ctx, en);
+}
+
+static inline void dphy_hal_datalane_ulps_exit(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->datalane_ulps_exit)
+ ppi->datalane_ulps_exit(ctx, en);
+}
+
+/*
+ * Configure minimum wait period for HS transmission request after a stop state
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param no_of_byte_cycles [in byte (lane) clock cycles]
+ */
+static inline void dphy_hal_stop_wait_time(struct sprd_dphy *dphy, u8 cycles)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->stop_wait_time)
+ ppi->stop_wait_time(ctx, cycles);
+}
+
+/*
+ * Set number of active lanes
+ * @param dphy: pointer to structure
+ * which holds information about the d-dphy module
+ * @param no_of_lanes
+ */
+static inline void dphy_hal_datalane_en(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->datalane_en)
+ ppi->datalane_en(ctx);
+}
+
+/*
+ * Enable clock lane module
+ * @param dphy pointer to structure
+ * which holds information about the d-dphy module
+ * @param en
+ */
+static inline void dphy_hal_clklane_en(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->clklane_en)
+ ppi->clklane_en(ctx, en);
+}
+
+/*
+ * Request the PHY module to start transmission of high speed clock.
+ * This causes the clock lane to start transmitting DDR clock on the
+ * lane interconnect.
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param enable
+ * @note this function should be called explicitly by user always except for
+ * transmitting
+ */
+static inline void dphy_hal_clk_hs_rqst(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->clk_hs_rqst)
+ ppi->clk_hs_rqst(ctx, en);
+}
+
+/*
+ * Get D-PHY PPI status
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param mask
+ * @return status
+ */
+static inline u8 dphy_hal_is_pll_locked(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_pll_locked)
+ return 1;
+
+ return ppi->is_pll_locked(ctx);
+}
+
+static inline u8 dphy_hal_is_rx_direction(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_rx_direction)
+ return 0;
+
+ return ppi->is_rx_direction(ctx);
+}
+
+static inline u8 dphy_hal_is_rx_ulps_esc_lane0(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_rx_ulps_esc_lane0)
+ return 0;
+
+ return ppi->is_rx_ulps_esc_lane0(ctx);
+}
+
+static inline u8 dphy_hal_is_stop_state_clklane(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_stop_state_clklane)
+ return 1;
+
+ return ppi->is_stop_state_clklane(ctx);
+}
+
+static inline u8 dphy_hal_is_stop_state_datalane(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_stop_state_datalane)
+ return 1;
+
+ return ppi->is_stop_state_datalane(ctx);
+}
+
+static inline u8 dphy_hal_is_ulps_active_clklane(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_ulps_active_clklane)
+ return 1;
+
+ return ppi->is_ulps_active_clklane(ctx);
+}
+
+static inline u8 dphy_hal_is_ulps_active_datalane(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_ulps_active_datalane)
+ return 1;
+
+ return ppi->is_ulps_active_datalane(ctx);
+}
+
+/*
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param value
+ */
+static inline void dphy_hal_test_clk(struct sprd_dphy *dphy, u8 level)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->tst_clk)
+ ppi->tst_clk(ctx, level);
+}
+
+/*
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param value
+ */
+static inline void dphy_hal_test_clr(struct sprd_dphy *dphy, u8 level)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->tst_clr)
+ ppi->tst_clr(ctx, level);
+}
+
+/*
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param on_falling_edge
+ */
+static inline void dphy_hal_test_en(struct sprd_dphy *dphy, u8 level)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->tst_en)
+ ppi->tst_en(ctx, level);
+}
+
+/*
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ */
+static inline u8 dphy_hal_test_dout(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->tst_dout)
+ return ppi->tst_dout(ctx);
+
+ return 0;
+}
+
+/*
+ * @param dphy pointer to structure which holds information about the d-dphy
+ * module
+ * @param test_data
+ */
+static inline void dphy_hal_test_din(struct sprd_dphy *dphy, u8 data)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->tst_din)
+ ppi->tst_din(ctx, data);
+}
+
+static inline void dphy_hal_bist_en(struct sprd_dphy *dphy, int en)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (ppi->bist_en)
+ ppi->bist_en(ctx, en);
+}
+
+static inline u8 dphy_hal_is_bist_ok(struct sprd_dphy *dphy)
+{
+ struct dphy_ppi_ops *ppi = dphy->ppi;
+ struct dphy_context *ctx = &dphy->ctx;
+
+ if (!ppi->is_bist_ok)
+ return 1;
+
+ return ppi->is_bist_ok(ctx);
+}
+
diff --git a/drivers/gpu/drm/sprd/dsi/Makefile b/drivers/gpu/drm/sprd/dsi/Makefile
new file mode 100644
index 0000000..b7999b5
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+subdir-ccflags-y += -I$(src)
+
+obj-y += sprd_dsi_api.o
+
+obj-y += core/
diff --git a/drivers/gpu/drm/sprd/dsi/core/Makefile b/drivers/gpu/drm/sprd/dsi/core/Makefile
new file mode 100644
index 0000000..8a2957f
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/core/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += dsi_ctrl_r1p0.o dsi_ctrl_r1p0_ppi.o
\ No newline at end of file
diff --git a/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c b/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c
new file mode 100644
index 0000000..9653b0c
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c
@@ -0,0 +1,1186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+
+#include "sprd_dsi.h"
+#include "dsi_ctrl_r1p0.h"
+
+/*
+ * Get DSI Host core version
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return ascii number of the version
+ */
+static bool dsi_check_version(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ u32 version = readl(&reg->DSI_VERSION);
+
+ if (version == 0x100)
+ return true;
+ else if (version == 0x200)
+ return true;
+ else if (version == 0x300)
+ return true;
+ else
+ return false;
+}
+/*
+ * Modify power status of DSI Host core
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param on (1) or off (0)
+ */
+static void dsi_power_enable(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(enable, &reg->SOFT_RESET);
+}
+/*
+ * Enable/disable DPI video mode
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable (1) - disable (0)
+ */
+static void dsi_video_mode(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(0, &reg->DSI_MODE_CFG);
+}
+/*
+ * Enable command mode (Generic interface)
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_cmd_mode(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(1, &reg->DSI_MODE_CFG);
+}
+
+static bool dsi_is_cmd_mode(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ return readl(&reg->DSI_MODE_CFG);
+}
+/*
+ * Configure the read back virtual channel for the generic interface
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param vc to listen to on the line
+ */
+static void dsi_rx_vcid(struct dsi_context *ctx, u8 vc)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x1C virtual_channel_id;
+
+ virtual_channel_id.val = readl(&reg->VIRTUAL_CHANNEL_ID);
+ virtual_channel_id.bits.gen_rx_vcid = vc;
+
+ writel(virtual_channel_id.val, &reg->VIRTUAL_CHANNEL_ID);
+}
+/*
+ * Write the DPI video virtual channel destination
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param vc virtual channel
+ */
+static void dsi_video_vcid(struct dsi_context *ctx, u8 vc)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x1C virtual_channel_id;
+
+ virtual_channel_id.val = readl(&reg->VIRTUAL_CHANNEL_ID);
+ virtual_channel_id.bits.video_pkt_vcid = vc;
+
+ writel(virtual_channel_id.val, &reg->VIRTUAL_CHANNEL_ID);
+}
+/*
+ * Set DPI video mode type (burst/non-burst - with sync pulses or events)
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param type
+ * @return error code
+ */
+static void dsi_dpi_video_burst_mode(struct dsi_context *ctx, int mode)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x38 vid_mode_cfg;
+
+ vid_mode_cfg.val = readl(&reg->VID_MODE_CFG);
+ vid_mode_cfg.bits.vid_mode_type = mode;
+
+ writel(vid_mode_cfg.val, &reg->VID_MODE_CFG);
+}
+/*
+ * Set DPI video color coding
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param color_coding enum (configuration and color depth)
+ * @return error code
+ */
+static void dsi_dpi_color_coding(struct dsi_context *ctx, int coding)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x20 dpi_video_format;
+
+ dpi_video_format.val = readl(&reg->DPI_VIDEO_FORMAT);
+ dpi_video_format.bits.dpi_video_mode_format = coding;
+
+ writel(dpi_video_format.val, &reg->DPI_VIDEO_FORMAT);
+}
+/*
+ * Set DPI loosely packetisation video (used only when color depth = 18
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_dpi_18_loosely_packet_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x20 dpi_video_format;
+
+ dpi_video_format.val = readl(&reg->DPI_VIDEO_FORMAT);
+ dpi_video_format.bits.loosely18_en = enable;
+
+ writel(dpi_video_format.val, &reg->DPI_VIDEO_FORMAT);
+}
+/*
+ * Configure the Horizontal Line time
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle taken to transmit the total of the horizontal line
+ */
+static void dsi_dpi_hline_time(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x2C video_line_time;
+
+ video_line_time.val = readl(&reg->VIDEO_LINE_TIME);
+ video_line_time.bits.video_line_time = byte_cycle;
+
+ writel(video_line_time.val, &reg->VIDEO_LINE_TIME);
+}
+/*
+ * Configure the Horizontal back porch time
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle taken to transmit the horizontal back porch
+ */
+static void dsi_dpi_hbp_time(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x28 video_line_hblk_time;
+
+ video_line_hblk_time.val = readl(&reg->VIDEO_LINE_HBLK_TIME);
+ video_line_hblk_time.bits.video_line_hbp_time = byte_cycle;
+
+ writel(video_line_hblk_time.val, &reg->VIDEO_LINE_HBLK_TIME);
+}
+/*
+ * Configure the Horizontal sync time
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle taken to transmit the horizontal sync
+ */
+static void dsi_dpi_hsync_time(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x28 video_line_hblk_time;
+
+ video_line_hblk_time.val = readl(&reg->VIDEO_LINE_HBLK_TIME);
+ video_line_hblk_time.bits.video_line_hsa_time = byte_cycle;
+
+ writel(video_line_hblk_time.val, &reg->VIDEO_LINE_HBLK_TIME);
+}
+/*
+ * Configure the vertical active lines of the video stream
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param lines
+ */
+static void dsi_dpi_vact(struct dsi_context *ctx, u16 lines)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x34 video_active_lines;
+
+ video_active_lines.val = readl(&reg->VIDEO_VACTIVE_LINES);
+ video_active_lines.bits.vactive_lines = lines;
+
+ writel(video_active_lines.val, &reg->VIDEO_VACTIVE_LINES);
+}
+/*
+ * Configure the vertical front porch lines of the video stream
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param lines
+ */
+static void dsi_dpi_vfp(struct dsi_context *ctx, u16 lines)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x30 video_vblk_lines;
+
+ video_vblk_lines.val = readl(&reg->VIDEO_VBLK_LINES);
+ video_vblk_lines.bits.vfp_lines = lines;
+
+ writel(video_vblk_lines.val, &reg->VIDEO_VBLK_LINES);
+}
+/*
+ * Configure the vertical back porch lines of the video stream
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param lines
+ */
+static void dsi_dpi_vbp(struct dsi_context *ctx, u16 lines)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x30 video_vblk_lines;
+
+ video_vblk_lines.val = readl(&reg->VIDEO_VBLK_LINES);
+ video_vblk_lines.bits.vbp_lines = lines;
+
+ writel(video_vblk_lines.val, &reg->VIDEO_VBLK_LINES);
+}
+/*
+ * Configure the vertical sync lines of the video stream
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param lines
+ */
+static void dsi_dpi_vsync(struct dsi_context *ctx, u16 lines)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x30 video_vblk_lines;
+
+ video_vblk_lines.val = readl(&reg->VIDEO_VBLK_LINES);
+ video_vblk_lines.bits.vsa_lines = lines;
+
+ writel(video_vblk_lines.val, &reg->VIDEO_VBLK_LINES);
+}
+/*
+ * Enable return to low power mode inside horizontal front porch periods when
+ * timing allows
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable (1) - disable (0)
+ */
+static void dsi_dpi_hporch_lp_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x38 vid_mode_cfg;
+
+ vid_mode_cfg.val = readl(&reg->VID_MODE_CFG);
+
+ vid_mode_cfg.bits.lp_hfp_en = enable;
+ vid_mode_cfg.bits.lp_hbp_en = enable;
+
+ writel(vid_mode_cfg.val, &reg->VID_MODE_CFG);
+}
+/*
+ * Enable return to low power mode inside vertical active lines periods when
+ * timing allows
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable (1) - disable (0)
+ */
+static void dsi_dpi_vporch_lp_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x38 vid_mode_cfg;
+
+ vid_mode_cfg.val = readl(&reg->VID_MODE_CFG);
+
+ vid_mode_cfg.bits.lp_vact_en = enable;
+ vid_mode_cfg.bits.lp_vfp_en = enable;
+ vid_mode_cfg.bits.lp_vbp_en = enable;
+ vid_mode_cfg.bits.lp_vsa_en = enable;
+
+ writel(vid_mode_cfg.val, &reg->VID_MODE_CFG);
+}
+/*
+ * Enable FRAME BTA ACK
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable (1) - disable (0)
+ */
+static void dsi_dpi_frame_ack_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x38 vid_mode_cfg;
+
+ vid_mode_cfg.val = readl(&reg->VID_MODE_CFG);
+ vid_mode_cfg.bits.frame_bta_ack_en = enable;
+
+ writel(vid_mode_cfg.val, &reg->VID_MODE_CFG);
+}
+/*
+ * Write no of chunks to core - taken into consideration only when multi packet
+ * is enabled
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no of chunks
+ */
+static void dsi_dpi_chunk_num(struct dsi_context *ctx, u16 num)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x24 video_pkt_config;
+
+ video_pkt_config.val = readl(&reg->VIDEO_PKT_CONFIG);
+ video_pkt_config.bits.video_line_chunk_num = num;
+
+ writel(video_pkt_config.val, &reg->VIDEO_PKT_CONFIG);
+}
+/*
+ * Write the null packet size - will only be taken into account when null
+ * packets are enabled.
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param size of null packet
+ * @return error code
+ */
+static void dsi_dpi_null_packet_size(struct dsi_context *ctx, u16 size)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xC0 video_nullpkt_size;
+
+ video_nullpkt_size.val = readl(&reg->VIDEO_NULLPKT_SIZE);
+ video_nullpkt_size.bits.video_nullpkt_size = size;
+
+ writel(video_nullpkt_size.val, &reg->VIDEO_NULLPKT_SIZE);
+}
+/*
+ * Write video packet size. obligatory for sending video
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param size of video packet - containing information
+ * @return error code
+ */
+static void dsi_dpi_video_packet_size(struct dsi_context *ctx, u16 size)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x24 video_pkt_config;
+
+ video_pkt_config.val = readl(&reg->VIDEO_PKT_CONFIG);
+ video_pkt_config.bits.video_pkt_size = size;
+
+ writel(video_pkt_config.val, &reg->VIDEO_PKT_CONFIG);
+}
+/*
+ * Specifiy the size of the packet memory write start/continue
+ * @param instance pointer to structure holding the DSI Host core information
+ * @ size of the packet
+ * @note when different than zero (0) eDPI is enabled
+ */
+static void dsi_edpi_max_pkt_size(struct dsi_context *ctx, u16 size)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xC4 dcs_wm_pkt_size;
+
+ dcs_wm_pkt_size.val = readl(&reg->DCS_WM_PKT_SIZE);
+ dcs_wm_pkt_size.bits.dcs_wm_pkt_size = size;
+
+ writel(dcs_wm_pkt_size.val, &reg->DCS_WM_PKT_SIZE);
+}
+/*
+ * Enable tear effect acknowledge
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable (1) - disable (0)
+ */
+static void dsi_tear_effect_ack_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x68 cmd_mode_cfg;
+
+ cmd_mode_cfg.val = readl(&reg->CMD_MODE_CFG);
+ cmd_mode_cfg.bits.tear_fx_en = enable;
+
+ writel(cmd_mode_cfg.val, &reg->CMD_MODE_CFG);
+}
+/*
+ * Enable packets acknowledge request after each packet transmission
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable (1) - disable (0)
+ */
+static void dsi_cmd_ack_request_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x68 cmd_mode_cfg;
+
+ cmd_mode_cfg.val = readl(&reg->CMD_MODE_CFG);
+ cmd_mode_cfg.bits.ack_rqst_en = enable;
+
+ writel(cmd_mode_cfg.val, &reg->CMD_MODE_CFG);
+}
+/*
+ * Set DCS command packet transmission to transmission type
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_param of command
+ * @param lp transmit in low power
+ * @return error code
+ */
+static void dsi_cmd_mode_lp_cmd_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x68 cmd_mode_cfg;
+
+ cmd_mode_cfg.val = readl(&reg->CMD_MODE_CFG);
+
+ cmd_mode_cfg.bits.gen_sw_0p_tx = enable;
+ cmd_mode_cfg.bits.gen_sw_1p_tx = enable;
+ cmd_mode_cfg.bits.gen_sw_2p_tx = enable;
+ cmd_mode_cfg.bits.gen_lw_tx = enable;
+ cmd_mode_cfg.bits.dcs_sw_0p_tx = enable;
+ cmd_mode_cfg.bits.dcs_sw_1p_tx = enable;
+ cmd_mode_cfg.bits.dcs_lw_tx = enable;
+ cmd_mode_cfg.bits.max_rd_pkt_size = enable;
+
+ cmd_mode_cfg.bits.gen_sr_0p_tx = enable;
+ cmd_mode_cfg.bits.gen_sr_1p_tx = enable;
+ cmd_mode_cfg.bits.gen_sr_2p_tx = enable;
+ cmd_mode_cfg.bits.dcs_sr_0p_tx = enable;
+
+ writel(cmd_mode_cfg.val, &reg->CMD_MODE_CFG);
+}
+/*
+ * Set DCS read command packet transmission to transmission type
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_param of command
+ * @param lp transmit in low power
+ * @return error code
+ */
+static void dsi_video_mode_lp_cmd_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x38 vid_mode_cfg;
+
+ vid_mode_cfg.val = readl(&reg->VID_MODE_CFG);
+ vid_mode_cfg.bits.lp_cmd_en = enable;
+
+ writel(vid_mode_cfg.val, &reg->VID_MODE_CFG);
+}
+
+/*
+ * Write command header in the generic interface
+ * (which also sends DCS commands) as a subset
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param vc of destination
+ * @param packet_type (or type of DCS command)
+ * @param ls_byte (if DCS, it is the DCS command)
+ * @param ms_byte (only parameter of short DCS packet)
+ * @return error code
+ */
+static void dsi_set_packet_header(struct dsi_context *ctx,
+ u8 vc,
+ u8 type,
+ u8 wc_lsb,
+ u8 wc_msb)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x6C gen_hdr;
+
+ gen_hdr.bits.gen_dt = type;
+ gen_hdr.bits.gen_vc = vc;
+ gen_hdr.bits.gen_wc_lsbyte = wc_lsb;
+ gen_hdr.bits.gen_wc_msbyte = wc_msb;
+
+ writel(gen_hdr.val, &reg->GEN_HDR);
+}
+/*
+ * Write the payload of the long packet commands
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param payload array of bytes of payload
+ * @return error code
+ */
+static void dsi_set_packet_payload(struct dsi_context *ctx, u32 payload)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(payload, &reg->GEN_PLD_DATA);
+}
+/*
+ * Write the payload of the long packet commands
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param payload pointer to 32-bit array to hold read information
+ * @return error code
+ */
+static u32 dsi_get_rx_payload(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ return readl(&reg->GEN_PLD_DATA);
+}
+
+/*
+ * Enable Bus Turn-around request
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_bta_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(enable, &reg->TA_EN);
+}
+/*
+ * Enable EOTp reception
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_eotp_rx_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xBC eotp_en;
+
+ eotp_en.val = readl(&reg->EOTP_EN);
+ eotp_en.bits.rx_eotp_en = enable;
+
+ writel(eotp_en.val, &reg->EOTP_EN);
+}
+/*
+ * Enable EOTp transmission
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_eotp_tx_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xBC eotp_en;
+
+ eotp_en.val = readl(&reg->EOTP_EN);
+ eotp_en.bits.tx_eotp_en = enable;
+
+ writel(eotp_en.val, &reg->EOTP_EN);
+}
+/*
+ * Enable ECC reception, error correction and reporting
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_ecc_rx_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xB4 rx_pkt_check_config;
+
+ rx_pkt_check_config.val = readl(&reg->RX_PKT_CHECK_CONFIG);
+ rx_pkt_check_config.bits.rx_pkt_ecc_en = enable;
+
+ writel(rx_pkt_check_config.val, &reg->RX_PKT_CHECK_CONFIG);
+}
+/*
+ * Enable CRC reception, error reporting
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ */
+static void dsi_crc_rx_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xB4 rx_pkt_check_config;
+
+ rx_pkt_check_config.val = readl(&reg->RX_PKT_CHECK_CONFIG);
+ rx_pkt_check_config.bits.rx_pkt_crc_en = enable;
+
+ writel(rx_pkt_check_config.val, &reg->RX_PKT_CHECK_CONFIG);
+}
+/*
+ * NOTE: dsi-ctrl-r1p0 only
+ *
+ * Get status of read command
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if busy
+ */
+static bool dsi_is_rdcmd_done(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_rdcmd_done;
+}
+/*
+ * Get the FULL status of generic read payload fifo
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if fifo full
+ */
+static bool dsi_is_rx_payload_fifo_full(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_rdata_fifo_full;
+}
+/*
+ * Get the EMPTY status of generic read payload fifo
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if fifo empty
+ */
+static bool dsi_is_rx_payload_fifo_empty(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_rdata_fifo_empty;
+}
+/*
+ * Get the FULL status of generic write payload fifo
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if fifo full
+ */
+static bool dsi_is_tx_payload_fifo_full(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_wdata_fifo_full;
+}
+/*
+ * Get the EMPTY status of generic write payload fifo
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if fifo empty
+ */
+static bool dsi_is_tx_payload_fifo_empty(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_wdata_fifo_empty;
+}
+/*
+ * Get the FULL status of generic command fifo
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if fifo full
+ */
+static bool dsi_is_tx_cmd_fifo_full(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_cmd_fifo_full;
+}
+/*
+ * Get the EMPTY status of generic command fifo
+ * @param instance pointer to structure holding the DSI Host core information
+ * @return 1 if fifo empty
+ */
+static bool dsi_is_tx_cmd_fifo_empty(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x98 cmd_mode_status;
+
+ cmd_mode_status.val = readl(&reg->CMD_MODE_STATUS);
+
+ return cmd_mode_status.bits.gen_cmd_cmd_fifo_empty;
+}
+
+/* only if DPI */
+/*
+ * DPI interface signal delay config
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle period for waiting after controller receiving HSYNC from
+ * DPI interface to start read pixel data from memory.
+ */
+static void dsi_dpi_sig_delay(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xD0 video_sig_delay_config;
+
+ video_sig_delay_config.val = readl(&reg->VIDEO_SIG_DELAY_CONFIG);
+ video_sig_delay_config.bits.video_sig_delay = byte_cycle;
+
+ writel(video_sig_delay_config.val, &reg->VIDEO_SIG_DELAY_CONFIG);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch data lane from high speed to low power
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle
+ * @return error code
+ */
+static void dsi_datalane_hs2lp_config(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xAC phy_datalane_time_config;
+
+ phy_datalane_time_config.val = readl(&reg->PHY_DATALANE_TIME_CONFIG);
+ phy_datalane_time_config.bits.phy_datalane_hs_to_lp_time = byte_cycle;
+
+ writel(phy_datalane_time_config.val, &reg->PHY_DATALANE_TIME_CONFIG);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch the data lane from to low power high speed
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle
+ * @return error code
+ */
+static void dsi_datalane_lp2hs_config(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xAC phy_datalane_time_config;
+
+ phy_datalane_time_config.val = readl(&reg->PHY_DATALANE_TIME_CONFIG);
+ phy_datalane_time_config.bits.phy_datalane_lp_to_hs_time = byte_cycle;
+
+ writel(phy_datalane_time_config.val, &reg->PHY_DATALANE_TIME_CONFIG);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch clock lane from high speed to low power
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle
+ * @return error code
+ */
+static void dsi_clklane_hs2lp_config(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xA8 phy_clklane_time_config;
+
+ phy_clklane_time_config.val = readl(&reg->PHY_CLKLANE_TIME_CONFIG);
+ phy_clklane_time_config.bits.phy_clklane_hs_to_lp_time = byte_cycle;
+
+ writel(phy_clklane_time_config.val, &reg->PHY_CLKLANE_TIME_CONFIG);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch clock lane from to low power high speed
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle
+ * @return error code
+ */
+static void dsi_clklane_lp2hs_config(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0xA8 phy_clklane_time_config;
+
+ phy_clklane_time_config.val = readl(&reg->PHY_CLKLANE_TIME_CONFIG);
+ phy_clklane_time_config.bits.phy_clklane_lp_to_hs_time = byte_cycle;
+
+ writel(phy_clklane_time_config.val, &reg->PHY_CLKLANE_TIME_CONFIG);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to turn the bus around to start receiving
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle
+ * @return error code
+ */
+static void dsi_max_read_time(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(byte_cycle, &reg->MAX_READ_TIME);
+}
+/*
+ * Enable the automatic mechanism to stop providing clock in the clock
+ * lane when time allows
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param enable
+ * @return error code
+ */
+static void dsi_nc_clk_en(struct dsi_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x74 phy_clk_lane_lp_ctrl;
+
+ phy_clk_lane_lp_ctrl.val = readl(&reg->PHY_CLK_LANE_LP_CTRL);
+ phy_clk_lane_lp_ctrl.bits.auto_clklane_ctrl_en = enable;
+
+ writel(phy_clk_lane_lp_ctrl.val, &reg->PHY_CLK_LANE_LP_CTRL);
+}
+/*
+ * Write transmission escape timeout
+ * a safe guard so that the state machine would reset if transmission
+ * takes too long
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param div
+ */
+static void dsi_tx_escape_division(struct dsi_context *ctx, u8 div)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(div, &reg->TX_ESC_CLK_CONFIG);
+}
+/* PRESP Time outs */
+/*
+ * configure timeout divisions (so they would have more clock ticks)
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param div no of hs cycles before transiting back to LP in
+ * (lane_clk / div)
+ */
+static void dsi_timeout_clock_division(struct dsi_context *ctx, u8 div)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(div, &reg->TIMEOUT_CNT_CLK_CONFIG);
+}
+/*
+ * Configure the Low power receive time out
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle (of byte cycles)
+ */
+static void dsi_lp_rx_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(byte_cycle, &reg->LRX_H_TO_CONFIG);
+}
+/*
+ * Configure a high speed transmission time out
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param byte_cycle (byte cycles)
+ */
+static void dsi_hs_tx_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(byte_cycle, &reg->HTX_TO_CONFIG);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after bus turn around
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_byte_cycles period for which the DWC_mipi_dsi_host keeps the
+ * link still, after sending a BTA operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static void dsi_bta_presp_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(byte_cycle, &reg->BTA_PRESP_TO_CONFIG);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after LP data
+ * transmission write requests
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_byte_cycles period for which the DWC_mipi_dsi_host keeps the
+ * link still, after sending a low power write operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static void dsi_lp_write_presp_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(byte_cycle, &reg->LPWR_PRESP_TO_CONFIG);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after LP data
+ * transmission read requests
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_byte_cycles period for which the DWC_mipi_dsi_host keeps the
+ * link still, after sending a low power read operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static void dsi_lp_read_presp_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x4C rd_presp_to_config;
+
+ rd_presp_to_config.val = readl(&reg->RD_PRESP_TO_CONFIG);
+ rd_presp_to_config.bits.lprd_presp_to_cnt_limit = byte_cycle;
+
+ writel(rd_presp_to_config.val, &reg->RD_PRESP_TO_CONFIG);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after HS data
+ * transmission write requests
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_byte_cycles period for which the DWC_mipi_dsi_host keeps the
+ * link still, after sending a high-speed write operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static void dsi_hs_write_presp_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x50 hswr_presp_to_config;
+
+ hswr_presp_to_config.val = readl(&reg->HSWR_PRESP_TO_CONFIG);
+ hswr_presp_to_config.bits.hswr_presp_to_cnt_limit = byte_cycle;
+
+ writel(hswr_presp_to_config.val, &reg->HSWR_PRESP_TO_CONFIG);
+}
+/*
+ * Timeout for peripheral between HS data transmission read requests
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param no_of_byte_cycles period for which the DWC_mipi_dsi_host keeps the
+ * link still, after sending a high-speed read operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static void dsi_hs_read_presp_timeout(struct dsi_context *ctx, u16 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x4C rd_presp_to_config;
+
+ rd_presp_to_config.val = readl(&reg->RD_PRESP_TO_CONFIG);
+ rd_presp_to_config.bits.hsrd_presp_to_cnt_limit = byte_cycle;
+
+ writel(rd_presp_to_config.val, &reg->RD_PRESP_TO_CONFIG);
+}
+/*
+ * Get the error 0 interrupt register status
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param mask the mask to be read from the register
+ * @return error status 0 value
+ */
+static u32 dsi_int0_status(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x08 protocol_int_sts;
+
+ protocol_int_sts.val = readl(&reg->PROTOCOL_INT_STS);
+ writel(protocol_int_sts.val, &reg->PROTOCOL_INT_CLR);
+
+ if (protocol_int_sts.bits.dphy_errors_0)
+ pr_err("dphy_err: escape entry error\n");
+
+ if (protocol_int_sts.bits.dphy_errors_1)
+ pr_err("dphy_err: lp data transmission sync error\n");
+
+ if (protocol_int_sts.bits.dphy_errors_2)
+ pr_err("dphy_err: control error\n");
+
+ if (protocol_int_sts.bits.dphy_errors_3)
+ pr_err("dphy_err: LP0 contention error\n");
+
+ if (protocol_int_sts.bits.dphy_errors_4)
+ pr_err("dphy_err: LP1 contention error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_0)
+ pr_err("ack_err: SoT error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_1)
+ pr_err("ack_err: SoT Sync error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_2)
+ pr_err("ack_err: EoT Sync error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_3)
+ pr_err("ack_err: Escape Mode Entry Command error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_4)
+ pr_err("ack_err: LP Transmit Sync error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_5)
+ pr_err("ack_err: Peripheral Timeout error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_6)
+ pr_err("ack_err: False Control error\n");
+
+ if (protocol_int_sts.bits.ack_with_err_7)
+ pr_err("ack_err: reserved (specific to device)\n");
+
+ if (protocol_int_sts.bits.ack_with_err_8)
+ pr_err("ack_err: ECC error, single-bit (corrected)\n");
+
+ if (protocol_int_sts.bits.ack_with_err_9)
+ pr_err("ack_err: ECC error, multi-bit (not corrected)\n");
+
+ if (protocol_int_sts.bits.ack_with_err_10)
+ pr_err("ack_err: checksum error (long packet only)\n");
+
+ if (protocol_int_sts.bits.ack_with_err_11)
+ pr_err("ack_err: not recognized DSI data type\n");
+
+ if (protocol_int_sts.bits.ack_with_err_12)
+ pr_err("ack_err: DSI VC ID Invalid\n");
+
+ if (protocol_int_sts.bits.ack_with_err_13)
+ pr_err("ack_err: invalid transmission length\n");
+
+ if (protocol_int_sts.bits.ack_with_err_14)
+ pr_err("ack_err: reserved (specific to device)\n");
+
+ if (protocol_int_sts.bits.ack_with_err_15)
+ pr_err("ack_err: DSI protocol violation\n");
+
+ return 0;
+}
+/*
+ * Get the error 1 interrupt register status
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param mask the mask to be read from the register
+ * @return error status 1 value
+ */
+static u32 dsi_int1_status(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+ union _0x10 internal_int_sts;
+ u32 status = 0;
+
+ internal_int_sts.val = readl(&reg->INTERNAL_INT_STS);
+ writel(internal_int_sts.val, &reg->INTERNAL_INT_CLR);
+
+ if (internal_int_sts.bits.receive_pkt_size_err)
+ pr_err("receive packet size error\n");
+
+ if (internal_int_sts.bits.eotp_not_receive_err)
+ pr_err("EoTp packet is not received\n");
+
+ if (internal_int_sts.bits.gen_cmd_cmd_fifo_wr_err)
+ pr_err("cmd header-fifo is full\n");
+
+ if (internal_int_sts.bits.gen_cmd_rdata_fifo_rd_err)
+ pr_err("cmd read-payload-fifo is empty\n");
+
+ if (internal_int_sts.bits.gen_cmd_rdata_fifo_wr_err)
+ pr_err("cmd read-payload-fifo is full\n");
+
+ if (internal_int_sts.bits.gen_cmd_wdata_fifo_wr_err)
+ pr_err("cmd write-payload-fifo is full\n");
+
+ if (internal_int_sts.bits.gen_cmd_wdata_fifo_rd_err)
+ pr_err("cmd write-payload-fifo is empty\n");
+
+ if (internal_int_sts.bits.dpi_pix_fifo_wr_err) {
+ pr_err("DPI pixel-fifo is full\n");
+ status |= DSI_INT_STS_NEED_SOFT_RESET;
+ }
+
+ if (internal_int_sts.bits.ecc_single_err)
+ pr_err("ECC single error in a received packet\n");
+
+ if (internal_int_sts.bits.ecc_multi_err)
+ pr_err("ECC multiple error in a received packet\n");
+
+ if (internal_int_sts.bits.crc_err)
+ pr_err("CRC error in the received packet payload\n");
+
+ if (internal_int_sts.bits.hs_tx_timeout)
+ pr_err("high-speed transmission timeout\n");
+
+ if (internal_int_sts.bits.lp_rx_timeout)
+ pr_err("low-power reception timeout\n");
+
+ return status;
+}
+/*
+ * Get the error 1 interrupt register status
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param mask the mask to be read from the register
+ * @return error status 1 value
+ */
+static u32 dsi_int2_status(struct dsi_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ if (readl(&reg->INT_PLL_STS))
+ pr_err("pll interrupt\n");
+
+ return 0;
+}
+/*
+ * Configure MASK (hiding) of interrupts coming from error 0 source
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param mask to be written to the register
+ */
+static void dsi_int0_mask(struct dsi_context *ctx, u32 mask)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(mask, &reg->MASK_PROTOCOL_INT);
+}
+/*
+ * Configure MASK (hiding) of interrupts coming from error 1 source
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param mask the mask to be written to the register
+ */
+static void dsi_int1_mask(struct dsi_context *ctx, u32 mask)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(mask, &reg->MASK_INTERNAL_INT);
+}
+/*
+ * Configure MASK (hiding) of interrupts coming from error 2 source
+ * @param instance pointer to structure holding the DSI Host core information
+ * @param mask the mask to be written to the register
+ */
+static void dsi_int2_mask(struct dsi_context *ctx, u32 mask)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->base;
+
+ writel(mask, &reg->INT_PLL_MSK);
+}
+
+static struct dsi_core_ops dsi_ctrl_ops = {
+ .check_version = dsi_check_version,
+ .power_en = dsi_power_enable,
+ .video_mode = dsi_video_mode,
+ .cmd_mode = dsi_cmd_mode,
+ .is_cmd_mode = dsi_is_cmd_mode,
+ .rx_vcid = dsi_rx_vcid,
+ .video_vcid = dsi_video_vcid,
+ .dpi_video_burst_mode = dsi_dpi_video_burst_mode,
+ .dpi_color_coding = dsi_dpi_color_coding,
+ .dpi_18_loosely_packet_en = dsi_dpi_18_loosely_packet_en,
+ .dpi_sig_delay = dsi_dpi_sig_delay,
+ .dpi_hline_time = dsi_dpi_hline_time,
+ .dpi_hsync_time = dsi_dpi_hsync_time,
+ .dpi_hbp_time = dsi_dpi_hbp_time,
+ .dpi_vact = dsi_dpi_vact,
+ .dpi_vfp = dsi_dpi_vfp,
+ .dpi_vbp = dsi_dpi_vbp,
+ .dpi_vsync = dsi_dpi_vsync,
+ .dpi_hporch_lp_en = dsi_dpi_hporch_lp_en,
+ .dpi_vporch_lp_en = dsi_dpi_vporch_lp_en,
+ .dpi_frame_ack_en = dsi_dpi_frame_ack_en,
+ .dpi_chunk_num = dsi_dpi_chunk_num,
+ .dpi_null_packet_size = dsi_dpi_null_packet_size,
+ .dpi_video_packet_size = dsi_dpi_video_packet_size,
+ .edpi_max_pkt_size = dsi_edpi_max_pkt_size,
+ .tear_effect_ack_en = dsi_tear_effect_ack_en,
+ .cmd_ack_request_en = dsi_cmd_ack_request_en,
+ .cmd_mode_lp_cmd_en = dsi_cmd_mode_lp_cmd_en,
+ .video_mode_lp_cmd_en = dsi_video_mode_lp_cmd_en,
+ .set_packet_header = dsi_set_packet_header,
+ .set_packet_payload = dsi_set_packet_payload,
+ .get_rx_payload = dsi_get_rx_payload,
+ .bta_en = dsi_bta_en,
+ .eotp_rx_en = dsi_eotp_rx_en,
+ .eotp_tx_en = dsi_eotp_tx_en,
+ .ecc_rx_en = dsi_ecc_rx_en,
+ .crc_rx_en = dsi_crc_rx_en,
+ .is_bta_returned = dsi_is_rdcmd_done,
+ .is_rx_payload_fifo_full = dsi_is_rx_payload_fifo_full,
+ .is_rx_payload_fifo_empty = dsi_is_rx_payload_fifo_empty,
+ .is_tx_payload_fifo_full = dsi_is_tx_payload_fifo_full,
+ .is_tx_payload_fifo_empty = dsi_is_tx_payload_fifo_empty,
+ .is_tx_cmd_fifo_full = dsi_is_tx_cmd_fifo_full,
+ .is_tx_cmd_fifo_empty = dsi_is_tx_cmd_fifo_empty,
+ .datalane_hs2lp_config = dsi_datalane_hs2lp_config,
+ .datalane_lp2hs_config = dsi_datalane_lp2hs_config,
+ .clklane_hs2lp_config = dsi_clklane_hs2lp_config,
+ .clklane_lp2hs_config = dsi_clklane_lp2hs_config,
+ .max_read_time = dsi_max_read_time,
+ .nc_clk_en = dsi_nc_clk_en,
+ .tx_escape_division = dsi_tx_escape_division,
+ .timeout_clock_division = dsi_timeout_clock_division,
+ .lp_rx_timeout = dsi_lp_rx_timeout,
+ .hs_tx_timeout = dsi_hs_tx_timeout,
+ .bta_presp_timeout = dsi_bta_presp_timeout,
+ .lp_write_presp_timeout = dsi_lp_write_presp_timeout,
+ .lp_read_presp_timeout = dsi_lp_read_presp_timeout,
+ .hs_write_presp_timeout = dsi_hs_write_presp_timeout,
+ .hs_read_presp_timeout = dsi_hs_read_presp_timeout,
+ .int0_status = dsi_int0_status,
+ .int1_status = dsi_int1_status,
+ .int2_status = dsi_int2_status,
+ .int0_mask = dsi_int0_mask,
+ .int1_mask = dsi_int1_mask,
+ .int2_mask = dsi_int2_mask,
+};
+
+static struct ops_entry entry = {
+ .ver = "sprd,dsi-ctrl",
+ .ops = &dsi_ctrl_ops,
+};
+
+static int __init dsi_core_register(void)
+{
+ return dsi_core_ops_register(&entry);
+}
+
+subsys_initcall(dsi_core_register);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("DSI Low-level registers operation for Unisoc DSI_CTRL RXP0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h b/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h
new file mode 100644
index 0000000..ede010b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h
@@ -0,0 +1,1417 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _MIPI_DSI_R1P0_H_
+#define _MIPI_DSI_R1P0_H_
+
+#include <asm/types.h>
+
+struct dsi_reg {
+ union _0x00 {
+ u32 val;
+ struct _DSI_VERSION {
+ u32 dsi_version: 16;
+ u32 reserved: 16;
+ } bits;
+ } DSI_VERSION;
+
+ union _0x04 {
+ u32 val;
+ struct _SOFT_RESET {
+ /*
+ * This bit configures the core either to work normal or to
+ * reset. It's default value is 0. After the core configur-
+ * ation, to enable the mipi_dsi_host, set this register to 1.
+ * 1: power up 0: reset core
+ */
+ u32 dsi_soft_reset: 1;
+
+ u32 reserved: 31;
+ } bits;
+ } SOFT_RESET;
+
+ union _0x08 {
+ u32 val;
+ struct _PROTOCOL_INT_STS {
+ /* ErrEsc escape entry error from Lane 0 */
+ u32 dphy_errors_0: 1;
+
+ /* ErrSyncEsc low-power data transmission synchronization
+ * error from Lane 0
+ */
+ u32 dphy_errors_1: 1;
+
+ /* ErrControl error from Lane 0 */
+ u32 dphy_errors_2: 1;
+
+ /* ErrContentionLP0 LP0 contention error from Lane 0 */
+ u32 dphy_errors_3: 1;
+
+ /* ErrContentionLP1 LP1 contention error from Lane 0 */
+ u32 dphy_errors_4: 1;
+
+ /* debug mode protocol errors */
+ u32 protocol_debug_err: 11;
+
+ /* SoT error from the Acknowledge error report */
+ u32 ack_with_err_0: 1;
+
+ /* SoT Sync error from the Acknowledge error report */
+ u32 ack_with_err_1: 1;
+
+ /* EoT Sync error from the Acknowledge error report */
+ u32 ack_with_err_2: 1;
+
+ /* Escape Mode Entry Command error from the Acknowledge
+ * error report
+ */
+ u32 ack_with_err_3: 1;
+
+ /* LP Transmit Sync error from the Acknowledge error report */
+ u32 ack_with_err_4: 1;
+
+ /* Peripheral Timeout error from the Acknowledge error report */
+ u32 ack_with_err_5: 1;
+
+ /* False Control error from the Acknowledge error report */
+ u32 ack_with_err_6: 1;
+
+ /* reserved (specific to device) from the Acknowledge error
+ * report
+ */
+ u32 ack_with_err_7: 1;
+
+ /* ECC error, single-bit (detected and corrected) from the
+ * Acknowledge error report
+ */
+ u32 ack_with_err_8: 1;
+
+ /* ECC error, multi-bit (detected, not corrected) from the
+ * Acknowledge error report
+ */
+ u32 ack_with_err_9: 1;
+
+ /* checksum error (long packet only) from the Acknowledge
+ * error report
+ */
+ u32 ack_with_err_10: 1;
+
+ /* not recognized DSI data type from the Acknowledge error
+ * report
+ */
+ u32 ack_with_err_11: 1;
+
+ /* DSI VC ID Invalid from the Acknowledge error report */
+ u32 ack_with_err_12: 1;
+
+ /* invalid transmission length from the Acknowledge error
+ * report
+ */
+ u32 ack_with_err_13: 1;
+
+ /* reserved (specific to device) from the Acknowledge error
+ * report
+ */
+ u32 ack_with_err_14: 1;
+
+ /* DSI protocol violation from the Acknowledge error report */
+ u32 ack_with_err_15: 1;
+
+ } bits;
+ } PROTOCOL_INT_STS;
+
+ union _0x0C {
+ u32 val;
+ struct _MASK_PROTOCOL_INT {
+ u32 mask_dphy_errors_0: 1;
+ u32 mask_dphy_errors_1: 1;
+ u32 mask_dphy_errors_2: 1;
+ u32 mask_dphy_errors_3: 1;
+ u32 mask_dphy_errors_4: 1;
+ u32 mask_protocol_debug_err: 11;
+ u32 mask_ack_with_err_0: 1;
+ u32 mask_ack_with_err_1: 1;
+ u32 mask_ack_with_err_2: 1;
+ u32 mask_ack_with_err_3: 1;
+ u32 mask_ack_with_err_4: 1;
+ u32 mask_ack_with_err_5: 1;
+ u32 mask_ack_with_err_6: 1;
+ u32 mask_ack_with_err_7: 1;
+ u32 mask_ack_with_err_8: 1;
+ u32 mask_ack_with_err_9: 1;
+ u32 mask_ack_with_err_10: 1;
+ u32 mask_ack_with_err_11: 1;
+ u32 mask_ack_with_err_12: 1;
+ u32 mask_ack_with_err_13: 1;
+ u32 mask_ack_with_err_14: 1;
+ u32 mask_ack_with_err_15: 1;
+ } bits;
+ } MASK_PROTOCOL_INT;
+
+ union _0x10 {
+ u32 val;
+ struct _INTERNAL_INT_STS {
+ /* This bit indicates that the packet size error is detected
+ * during the packet reception.
+ */
+ u32 receive_pkt_size_err: 1;
+
+ /* This bit indicates that the EoTp packet is not received at
+ * the end of the incoming peripheral transmission
+ */
+ u32 eotp_not_receive_err: 1;
+
+ /* This bit indicates that the system tried to write a command
+ * through the Generic interface and the FIFO is full. There-
+ * fore, the command is not written.
+ */
+ u32 gen_cmd_cmd_fifo_wr_err: 1;
+
+ /* This bit indicates that during a DCS read data, the payload
+ * FIFO becomes empty and the data sent to the interface is
+ * corrupted.
+ */
+ u32 gen_cmd_rdata_fifo_rd_err: 1;
+
+ /* This bit indicates that during a generic interface packet
+ * read back, the payload FIFO becomes full and the received
+ * data is corrupted.
+ */
+ u32 gen_cmd_rdata_fifo_wr_err: 1;
+
+ /* This bit indicates that the system tried to write a payload
+ * data through the Generic interface and the FIFO is full.
+ * Therefore, the payload is not written.
+ */
+ u32 gen_cmd_wdata_fifo_wr_err: 1;
+
+ /* This bit indicates that during a Generic interface packet
+ * build, the payload FIFO becomes empty and corrupt data is
+ * sent.
+ */
+ u32 gen_cmd_wdata_fifo_rd_err: 1;
+
+ /* This bit indicates that during a DPI pixel line storage,
+ * the payload FIFO becomes full and the data stored is
+ * corrupted.
+ */
+ u32 dpi_pix_fifo_wr_err: 1;
+
+ /* internal debug error */
+ u32 internal_debug_err: 19;
+
+ /* This bit indicates that the ECC single error is detected
+ * and corrected in a received packet.
+ */
+ u32 ecc_single_err: 1;
+
+ /* This bit indicates that the ECC multiple error is detected
+ * in a received packet.
+ */
+ u32 ecc_multi_err: 1;
+
+ /* This bit indicates that the CRC error is detected in the
+ * received packet payload.
+ */
+ u32 crc_err: 1;
+
+ /* This bit indicates that the high-speed transmission timeout
+ * counter reached the end and contention is detected.
+ */
+ u32 hs_tx_timeout: 1;
+
+ /* This bit indicates that the low-power reception timeout
+ * counter reached the end and contention is detected.
+ */
+ u32 lp_rx_timeout: 1;
+
+ } bits;
+ } INTERNAL_INT_STS;
+
+ union _0x14 {
+ u32 val;
+ struct _MASK_INTERNAL_INT {
+ u32 mask_receive_pkt_size_err: 1;
+ u32 mask_eopt_not_receive_err: 1;
+ u32 mask_gen_cmd_cmd_fifo_wr_err: 1;
+ u32 mask_gen_cmd_rdata_fifo_rd_err: 1;
+ u32 mask_gen_cmd_rdata_fifo_wr_err: 1;
+ u32 mask_gen_cmd_wdata_fifo_wr_err: 1;
+ u32 mask_gen_cmd_wdata_fifo_rd_err: 1;
+ u32 mask_dpi_pix_fifo_wr_err: 1;
+ u32 mask_internal_debug_err: 19;
+ u32 mask_ecc_single_err: 1;
+ u32 mask_ecc_multi_err: 1;
+ u32 mask_crc_err: 1;
+ u32 mask_hs_tx_timeout: 1;
+ u32 mask_lp_rx_timeout: 1;
+ } bits;
+ } MASK_INTERNAL_INT;
+
+ union _0x18 {
+ u32 val;
+ struct _DSI_MODE_CFG {
+ /* This bit configures the operation mode
+ * 0: Video mode ; 1: Command mode
+ */
+ u32 cmd_video_mode: 1;
+
+ u32 reserved: 31;
+
+ } bits;
+ } DSI_MODE_CFG;
+
+ union _0x1C {
+ u32 val;
+ struct _VIRTUAL_CHANNEL_ID {
+ /* This field indicates the Generic interface read-back
+ * virtual channel identification
+ */
+ u32 gen_rx_vcid: 2;
+
+ /* This field configures the DPI virtual channel id that
+ * is indexed to the VIDEO mode packets
+ */
+ u32 video_pkt_vcid: 2;
+
+ u32 reserved: 28;
+
+ } bits;
+ } VIRTUAL_CHANNEL_ID;
+
+ union _0x20 {
+ u32 val;
+ struct _DPI_VIDEO_FORMAT {
+ /*
+ * This field configures the DPI color coding as follows:
+ * 0000: 16-bit configuration 1
+ * 0001: 16-bit configuration 2
+ * 0010: 16-bit configuration 3
+ * 0011: 18-bit configuration 1
+ * 0100: 18-bit configuration 2
+ * 0101: 24-bit
+ * 0110: 20-bit YCbCr 4:2:2 loosely packed
+ * 0111: 24-bit YCbCr 4:2:2
+ * 1000: 16-bit YCbCr 4:2:2
+ * 1001: 30-bit
+ * 1010: 36-bit
+ * 1011: 12-bit YCbCr 4:2:0
+ * 1100: Compression Display Stream
+ * 1101-1111: 12-bit YCbCr 4:2:0
+ */
+ u32 dpi_video_mode_format: 6;
+
+ /* When set to 1, this bit activates loosely packed
+ * variant to 18-bit configurations
+ */
+ u32 loosely18_en: 1;
+
+ u32 reserved: 25;
+
+ } bits;
+ } DPI_VIDEO_FORMAT;
+
+ union _0x24 {
+ u32 val;
+ struct _VIDEO_PKT_CONFIG {
+ /*
+ * This field configures the number of pixels in a single
+ * video packet. For 18-bit not loosely packed data types,
+ * this number must be a multiple of 4. For YCbCr data
+ * types, it must be a multiple of 2, as described in the
+ * DSI specification.
+ */
+ u32 video_pkt_size: 16;
+
+ /*
+ * This register configures the number of chunks to be
+ * transmitted during a Line period (a chunk consists of
+ * a video packet and a null packet). If set to 0 or 1,
+ * the video line is transmitted in a single packet. If
+ * set to 1, the packet is part of a chunk, so a null packet
+ * follows it if vid_null_size > 0. Otherwise, multiple chunks
+ * are used to transmit each video line.
+ */
+ u32 video_line_chunk_num: 16;
+
+ } bits;
+ } VIDEO_PKT_CONFIG;
+
+ union _0x28 {
+ u32 val;
+ struct _VIDEO_LINE_HBLK_TIME {
+ /* This field configures the Horizontal Back Porch period
+ * in lane byte clock cycles
+ */
+ u32 video_line_hbp_time: 16;
+
+ /* This field configures the Horizontal Synchronism Active
+ * period in lane byte clock cycles
+ */
+ u32 video_line_hsa_time: 16;
+
+ } bits;
+ } VIDEO_LINE_HBLK_TIME;
+
+ union _0x2C {
+ u32 val;
+ struct _VIDEO_LINE_TIME {
+ /* This field configures the size of the total line time
+ * (HSA+HBP+HACT+HFP) counted in lane byte clock cycles
+ */
+ u32 video_line_time: 16;
+
+ u32 reserved: 16;
+
+ } bits;
+ } VIDEO_LINE_TIME;
+
+ union _0x30 {
+ u32 val;
+ struct _VIDEO_VBLK_LINES {
+ /* This field configures the Vertical Front Porch period
+ * measured in number of horizontal lines
+ */
+ u32 vfp_lines: 10;
+
+ /* This field configures the Vertical Back Porch period
+ * measured in number of horizontal lines
+ */
+ u32 vbp_lines: 10;
+
+ /* This field configures the Vertical Synchronism Active
+ * period measured in number of horizontal lines
+ */
+ u32 vsa_lines: 10;
+
+ u32 reserved: 2;
+
+ } bits;
+ } VIDEO_VBLK_LINES;
+
+ union _0x34 {
+ u32 val;
+ struct _VIDEO_VACTIVE_LINES {
+ /* This field configures the Vertical Active period measured
+ * in number of horizontal lines
+ */
+ u32 vactive_lines: 14;
+
+ u32 reserved: 18;
+
+ } bits;
+ } VIDEO_VACTIVE_LINES;
+
+ union _0x38 {
+ u32 val;
+ struct _VID_MODE_CFG {
+ /*
+ * This field indicates the video mode transmission type as
+ * follows:
+ * 00: Non-burst with sync pulses
+ * 01: Non-burst with sync events
+ * 10 and 11: Burst mode
+ */
+ u32 vid_mode_type: 2;
+
+ u32 reserved_0: 6;
+
+ /* When set to 1, this bit enables the return to low-power
+ * inside the VSA period when timing allows.
+ */
+ u32 lp_vsa_en: 1;
+
+ /* When set to 1, this bit enables the return to low-power
+ * inside the VBP period when timing allows.
+ */
+ u32 lp_vbp_en: 1;
+
+ /* When set to 1, this bit enables the return to low-power
+ * inside the VFP period when timing allows.
+ */
+ u32 lp_vfp_en: 1;
+
+ /* When set to 1, this bit enables the return to low-power
+ * inside the VACT period when timing allows.
+ */
+ u32 lp_vact_en: 1;
+
+ /* When set to 1, this bit enables the return to low-power
+ * inside the HBP period when timing allows.
+ */
+ u32 lp_hbp_en: 1;
+
+ /* When set to 1, this bit enables the return to low-power
+ * inside the HFP period when timing allows.
+ */
+ u32 lp_hfp_en: 1;
+
+ /* When set to 1, this bit enables the request for an ack-
+ * nowledge response at the end of a frame.
+ */
+ u32 frame_bta_ack_en: 1;
+
+ /* When set to 1, this bit enables the command transmission
+ * only in low-power mode.
+ */
+ u32 lp_cmd_en: 1;
+
+ u32 reserved_1: 16;
+
+ } bits;
+ } VID_MODE_CFG;
+
+ union _0x3C {
+ u32 val;
+ struct _SDF_MODE_CONFIG {
+ /*
+ * This field defines the 3D mode on/off & display orientation:
+ * 00: 3D mode off (2D mode on)
+ * 01: 3D mode on, portrait orientation
+ * 10: 3D mode on, landscape orientation
+ * 11: Reserved
+ */
+ u32 rf_3d_mode: 2;
+
+ /*
+ * This field defines the 3D image format:
+ * 00: Line (alternating lines of left and right data)
+ * 01: Frame (alternating frames of left and right data)
+ * 10: Pixel (alternating pixels of left and right data)
+ * 11: Reserved
+ */
+ u32 rf_3d_format: 2;
+
+ /*
+ * This field defines whether there is a second VSYNC pulse
+ * between Left and Right Images, when 3D Image Format is
+ * Frame-based:
+ * 0: No sync pulses between left and right data
+ * 1: Sync pulse (HSYNC, VSYNC, blanking) between left and
+ * right data
+ */
+ u32 second_vsync_en: 1;
+
+ /*
+ * This bit defines the left or right order:
+ * 0: Left eye data is sent first, and then the right eye data
+ * is sent.
+ * 1: Right eye data is sent first, and then the left eye data
+ * is sent.
+ */
+ u32 left_right_order: 1;
+
+ u32 reserved_0: 2;
+
+ /*
+ * When set, causes the next VSS packet to include 3D control
+ * payload in every VSS packet.
+ */
+ u32 rf_3d_payload_en: 1;
+
+ u32 reserved_1: 23;
+
+ } bits;
+ } SDF_MODE_CONFIG;
+
+ union _0x40 {
+ u32 val;
+ struct _TIMEOUT_CNT_CLK_CONFIG {
+ /*
+ * This field indicates the division factor for the Time Out
+ * clock used as the timing unit in the configuration of HS to
+ * LP and LP to HS transition error.
+ */
+ u32 timeout_cnt_clk_config: 16;
+
+ u32 reserved: 16;
+
+ } bits;
+ } TIMEOUT_CNT_CLK_CONFIG;
+
+ union _0x44 {
+ u32 val;
+ struct _HTX_TO_CONFIG {
+ /*
+ * This field configures the timeout counter that triggers
+ * a high speed transmission timeout contention detection
+ * (measured in TO_CLK_DIVISION cycles).
+ *
+ * If using the non-burst mode and there is no sufficient
+ * time to switch from HS to LP and back in the period which
+ * is from one line data finishing to the next line sync
+ * start, the DSI link returns the LP state once per frame,
+ * then you should configure the TO_CLK_DIVISION and
+ * hstx_to_cnt to be in accordance with:
+ * hstx_to_cnt * lanebyteclkperiod * TO_CLK_DIVISION >= the
+ * time of one FRAME data transmission * (1 + 10%)
+ *
+ * In burst mode, RGB pixel packets are time-compressed,
+ * leaving more time during a scan line. Therefore, if in
+ * burst mode and there is sufficient time to switch from HS
+ * to LP and back in the period of time from one line data
+ * finishing to the next line sync start, the DSI link can
+ * return LP mode and back in this time interval to save power.
+ * For this, configure the TO_CLK_DIVISION and hstx_to_cnt
+ * to be in accordance with:
+ * hstx_to_cnt * lanebyteclkperiod * TO_CLK_DIVISION >= the
+ * time of one LINE data transmission * (1 + 10%)
+ */
+ u32 htx_to_cnt_limit: 32;
+ } bits;
+ } HTX_TO_CONFIG;
+
+ union _0x48 {
+ u32 val;
+ struct _LRX_H_TO_CONFIG {
+ /*
+ * This field configures the timeout counter that triggers
+ * a low-power reception timeout contention detection (measured
+ * in TO_CLK_DIVISION cycles).
+ */
+ u32 lrx_h_to_cnt_limit: 32;
+ } bits;
+ } LRX_H_TO_CONFIG;
+
+ union _0x4C {
+ u32 val;
+ struct _RD_PRESP_TO_CONFIG {
+ /*
+ * This field sets a period for which the DWC_mipi_dsi_host
+ * keeps the link still, after sending a low-power read oper-
+ * ation. This period is measured in cycles of lanebyteclk.
+ * The counting starts when the D-PHY enters the Stop state
+ * and causes no interrupts.
+ */
+ u32 lprd_presp_to_cnt_limit: 16;
+
+ /*
+ * This field sets a period for which the DWC_mipi_dsi_host
+ * keeps the link still, after sending a high-speed read oper-
+ * ation. This period is measured in cycles of lanebyteclk.
+ * The counting starts when the D-PHY enters the Stop state
+ * and causes no interrupts.
+ */
+ u32 hsrd_presp_to_cnt_limit: 16;
+
+ } bits;
+ } RD_PRESP_TO_CONFIG;
+
+ union _0x50 {
+ u32 val;
+ struct _HSWR_PRESP_TO_CONFIG {
+ /*
+ * This field sets a period for which the DWC_mipi_dsi_host
+ * keeps the link inactive after sending a high-speed write
+ * operation. This period is measured in cycles of lanebyteclk.
+ * The counting starts when the D-PHY enters the Stop state
+ * and causes no interrupts.
+ */
+ u32 hswr_presp_to_cnt_limit: 16;
+
+ u32 reserved_0: 8;
+
+ /*
+ * When set to 1, this bit ensures that the peripheral response
+ * timeout caused by hs_wr_to_cnt is used only once per eDPI
+ * frame, when both the following conditions are met:
+ * dpivsync_edpiwms has risen and fallen.
+ * Packets originated from eDPI have been transmitted and its
+ * FIFO is empty again In this scenario no non-eDPI requests
+ * are sent to the D-PHY, even if there is traffic from generic
+ * or DBI ready to be sent, making it return to stop state.
+ * When it does so, PRESP_TO counter is activated and only when
+ * it finishes does the controller send any other traffic that
+ * is ready.
+ */
+ u32 hswr_presp_to_mode: 1;
+
+ u32 reserved_1: 7;
+
+ } bits;
+ } HSWR_PRESP_TO_CONFIG;
+
+ union _0x54 {
+ u32 val;
+ struct _LPWR_PRESP_TO_CONFIG {
+ /*
+ * This field sets a period for which the DWC_mipi_dsi_host
+ * keeps the link still, after sending a low-power write oper-
+ * ation. This period is measured in cycles of lanebyteclk.
+ * The counting starts when the D-PHY enters the Stop state
+ * and causes no interrupts.
+ */
+ u32 lpwr_presp_to_cnt_limit: 16;
+
+ u32 reserved: 16;
+
+ } bits;
+ } LPWR_PRESP_TO_CONFIG;
+
+ union _0x58 {
+ u32 val;
+ struct _BTA_PRESP_TO_CONFIG {
+ /*
+ * This field sets a period for which the DWC_mipi_dsi_host
+ * keeps the link still, after completing a Bus Turn-Around.
+ * This period is measured in cycles of lanebyteclk. The
+ * counting starts when the D-PHY enters the Stop state and
+ * causes no interrupts.
+ */
+ u32 bta_presp_to_cnt_limit: 16;
+
+ u32 reserved: 16;
+
+ } bits;
+ } BTA_PRESP_TO_CONFIG;
+
+ union _0x5C {
+ u32 val;
+ struct _TX_ESC_CLK_CONFIG {
+ /*
+ * This field indicates the division factor for the TX Escape
+ * clock source (lanebyteclk). The values 0 and 1 stop the
+ * TX_ESC clock generation.
+ */
+ u32 tx_esc_clk_config: 16;
+
+ u32 reserved: 16;
+
+ } bits;
+ } TX_ESC_CLK_CONFIG;
+
+ union _0x60 {
+ u32 val;
+ struct _VACT_CMD_TRANS_LIMIT {
+ /*
+ * This field is used for the transmission of commands in
+ * low-power mode. It defines the size, in bytes, of the
+ * largest packet that can fit in a line during the VACT
+ * region.
+ */
+ u32 vact_cmd_trans_limit: 8;
+
+ u32 reserved: 24;
+
+ } bits;
+ } VACT_CMD_TRANS_LIMIT;
+
+ union _0x64 {
+ u32 val;
+ struct _VBLK_CMD_TRANS_LIMIT {
+ /*
+ * This field is used for the transmission of commands in
+ * low-power mode. It defines the size, in bytes, of the
+ * largest packet that can fit in a line during the VSA, VBP,
+ * and VFP regions.
+ */
+ u32 vblk_cmd_trans_limit: 8;
+
+ u32 reserved: 24;
+
+ } bits;
+ } VBLK_CMD_TRANS_LIMIT;
+
+ union _0x68 {
+ u32 val;
+ struct _CMD_MODE_CFG {
+ /*
+ * When set to 1, this bit enables the tearing effect
+ * acknowledge request.
+ */
+ u32 tear_fx_en: 1;
+
+ /*
+ * When set to 1, this bit enables the acknowledge request
+ * after each packet transmission.
+ */
+ u32 ack_rqst_en: 1;
+
+ u32 reserved_0: 3;
+
+ u32 pps_tx: 1;
+ u32 exq_tx: 1;
+ u32 cmc_tx: 1;
+
+ /*
+ * This bit configures the Generic short write packet with
+ * zero parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_sw_0p_tx: 1;
+
+ /*
+ * This bit configures the Generic short write packet with
+ * one parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_sw_1p_tx: 1;
+
+ /*
+ * This bit configures the Generic short write packet with
+ * two parameters command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_sw_2p_tx: 1;
+
+ /*
+ * This bit configures the Generic short read packet with
+ * zero parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_sr_0p_tx: 1;
+
+ /*
+ * This bit configures the Generic short read packet with
+ * one parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_sr_1p_tx: 1;
+
+ /*
+ * This bit configures the Generic short read packet with
+ * two parameters command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_sr_2p_tx: 1;
+
+ /*
+ * This bit configures the Generic long write packet command
+ * transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 gen_lw_tx: 1;
+
+ u32 reserved_1: 1;
+
+ /*
+ * This bit configures the DCS short write packet with zero
+ * parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 dcs_sw_0p_tx: 1;
+
+ /*
+ * This bit configures the DCS short write packet with one
+ * parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 dcs_sw_1p_tx: 1;
+
+ /*
+ * This bit configures the DCS short read packet with zero
+ * parameter command transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 dcs_sr_0p_tx: 1;
+
+ /*
+ * This bit configures the DCS long write packet command
+ * transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 dcs_lw_tx: 1;
+
+ u32 reserved_2: 4;
+
+ /*
+ * This bit configures the maximum read packet size command
+ * transmission type:
+ * 0: High-speed 1: Low-power
+ */
+ u32 max_rd_pkt_size: 1;
+
+ u32 reserved_3: 7;
+
+ } bits;
+ } CMD_MODE_CFG;
+
+ union _0x6C {
+ u32 val;
+ struct _GEN_HDR {
+ /*
+ * This field configures the packet data type of the header
+ * packet.
+ */
+ u32 gen_dt: 6;
+
+ /*
+ * This field configures the virtual channel id of the header
+ * packet.
+ */
+ u32 gen_vc: 2;
+
+ /*
+ * This field configures the least significant byte of the
+ * header packet's Word count for long packets or data 0 for
+ * short packets.
+ */
+ u32 gen_wc_lsbyte: 8;
+
+ /*
+ * This field configures the most significant byte of the
+ * header packet's word count for long packets or data 1 for
+ * short packets.
+ */
+ u32 gen_wc_msbyte: 8;
+
+ u32 reserved: 8;
+
+ } bits;
+ } GEN_HDR;
+
+ union _0x70 {
+ u32 val;
+ struct _GEN_PLD_DATA {
+ /* This field indicates byte 1 of the packet payload. */
+ u32 gen_pld_b1: 8;
+
+ /* This field indicates byte 2 of the packet payload. */
+ u32 gen_pld_b2: 8;
+
+ /* This field indicates byte 3 of the packet payload. */
+ u32 gen_pld_b3: 8;
+
+ /* This field indicates byte 4 of the packet payload. */
+ u32 gen_pld_b4: 8;
+
+ } bits;
+ } GEN_PLD_DATA;
+
+ union _0x74 {
+ u32 val;
+ struct _PHY_CLK_LANE_LP_CTRL {
+ /* This bit controls the D-PHY PPI txrequestclkhs signal */
+ u32 phy_clklane_tx_req_hs: 1;
+
+ /* This bit enables the automatic mechanism to stop providing
+ * clock in the clock lane when time allows.
+ */
+ u32 auto_clklane_ctrl_en: 1;
+
+ u32 reserved: 30;
+ } bits;
+ } PHY_CLK_LANE_LP_CTRL;
+
+ union _0x78 {
+ u32 val;
+ struct _PHY_INTERFACE_CTRL {
+ /* When set to 0, this bit places the D-PHY macro in power-
+ * down state.
+ */
+ u32 rf_phy_shutdown: 1;
+
+ /* When set to 0, this bit places the digital section of the
+ * D-PHY in the reset state.
+ */
+ u32 rf_phy_reset_n: 1;
+
+ /* When set to 1, this bit enables the D-PHY Clock Lane
+ * module.
+ */
+ u32 rf_phy_clk_en: 1;
+
+ /* When the D-PHY is in ULPS, this bit enables the D-PHY PLL. */
+ u32 rf_phy_force_pll: 1;
+
+ /* ULPS mode Request on clock lane */
+ u32 rf_phy_clk_txrequlps: 1;
+
+ /* ULPS mode Exit on clock lane */
+ u32 rf_phy_clk_txexitulps: 1;
+
+ /* ULPS mode Request on all active data lanes */
+ u32 rf_phy_data_txrequlps: 1;
+
+ /* ULPS mode Exit on all active data lanes */
+ u32 rf_phy_data_txexitulps: 1;
+
+ u32 reserved: 24;
+ } bits;
+ } PHY_INTERFACE_CTRL;
+
+ union _0x7C {
+ u32 val;
+ struct _PHY_TX_TRIGGERS {
+ /* This field controls the trigger transmissions. */
+ u32 phy_tx_triggers: 4;
+
+ u32 reserved: 28;
+ } bits;
+ } PHY_TX_TRIGGERS;
+
+ union _0x80 {
+ u32 val;
+ struct _DESKEW_START {
+ u32 deskew_start: 1;
+ u32 reserved: 31;
+ } bits;
+ } DESKEW_START;
+
+ union _0x84 {
+ u32 val;
+ struct _DESKEW_MODE {
+ u32 deskew_mode: 2;
+ u32 reserved: 30;
+ } bits;
+ } DESKEW_MODE;
+
+ union _0x88 {
+ u32 val;
+ struct _DESKEW_TIME {
+ u32 deskew_time: 32;
+ } bits;
+ } DESKEW_TIME;
+
+ union _0x8C {
+ u32 val;
+ struct _DESKEW_PERIOD {
+ u32 deskew_period: 32;
+ } bits;
+ } DESKEW_PERIOD;
+
+ union _0x90 {
+ u32 val;
+ struct _DESKEW_BUSY {
+ u32 deskew_busy: 1;
+ u32 reserved: 31;
+ } bits;
+ } DESKEW_BUSY;
+
+ union _0x94 {
+ u32 val;
+ struct _DESKEW_LANE_MASK {
+ u32 deskew_lane0_mask: 1;
+ u32 deskew_lane1_mask: 1;
+ u32 deskew_lane2_mask: 1;
+ u32 deskew_lane3_mask: 1;
+ u32 reserved: 28;
+ } bits;
+ } DESKEW_LANE_MASK;
+
+ union _0x98 {
+ u32 val;
+ struct _CMD_MODE_STATUS {
+ /*
+ * This bit is set when a read command is issued and cleared
+ * when the entire response is stored in the FIFO.
+ * Value after reset: 0x0
+ *
+ * NOTE:
+ * For mipi-dsi-r1p0 IP, this bit is set immediately when
+ * the read cmd is set to the GEN_HDR register.
+ *
+ * For dsi-ctrl-r1p0 IP, this bit is set only after the read
+ * cmd was actually sent out from the controller.
+ */
+ u32 gen_cmd_rdcmd_ongoing: 1;
+
+ /*
+ * This bit indicates the empty status of the generic read
+ * payload FIFO.
+ * Value after reset: 0x1
+ */
+ u32 gen_cmd_rdata_fifo_empty: 1;
+
+ /*
+ * This bit indicates the full status of the generic read
+ * payload FIFO.
+ * Value after reset: 0x0
+ */
+ u32 gen_cmd_rdata_fifo_full: 1;
+
+ /*
+ * This bit indicates the empty status of the generic write
+ * payload FIFO.
+ * Value after reset: 0x1
+ */
+ u32 gen_cmd_wdata_fifo_empty: 1;
+
+ /*
+ * This bit indicates the full status of the generic write
+ * payload FIFO.
+ * Value after reset: 0x0
+ */
+ u32 gen_cmd_wdata_fifo_full: 1;
+
+ /*
+ * This bit indicates the empty status of the generic
+ * command FIFO.
+ * Value after reset: 0x1
+ */
+ u32 gen_cmd_cmd_fifo_empty: 1;
+
+ /*
+ * This bit indicates the full status of the generic
+ * command FIFO.
+ * Value after reset: 0x0
+ */
+ u32 gen_cmd_cmd_fifo_full: 1;
+
+ /*
+ * This bit is set when the entire response of read is
+ * stored in the rx payload FIFO. And it will be cleared
+ * automaticlly after read this bit each time.
+ * Value after reset: 0x0
+ *
+ * NOTE: this bit is just supported for dsi-ctrl-r1p0 IP
+ */
+ u32 gen_cmd_rdcmd_done: 1;
+
+ u32 reserved : 24;
+
+ } bits;
+ } CMD_MODE_STATUS;
+
+ union _0x9C {
+ u32 val;
+ struct _PHY_STATUS {
+ /* the status of phydirection D-PHY signal */
+ u32 phy_direction: 1;
+
+ /* the status of phylock D-PHY signal */
+ u32 phy_lock: 1;
+
+ /* the status of rxulpsesc0lane D-PHY signal */
+ u32 phy_rxulpsesc0lane: 1;
+
+ /* the status of phystopstateclklane D-PHY signal */
+ u32 phy_stopstateclklane: 1;
+
+ /* the status of phystopstate0lane D-PHY signal */
+ u32 phy_stopstate0lane: 1;
+
+ /* the status of phystopstate1lane D-PHY signal */
+ u32 phy_stopstate1lane: 1;
+
+ /* the status of phystopstate2lane D-PHY signal */
+ u32 phy_stopstate2lane: 1;
+
+ /* the status of phystopstate3lane D-PHY signal */
+ u32 phy_stopstate3lane: 1;
+
+ /* the status of phyulpsactivenotclk D-PHY signal */
+ u32 phy_ulpsactivenotclk: 1;
+
+ /* the status of ulpsactivenot0lane D-PHY signal */
+ u32 phy_ulpsactivenot0lane: 1;
+
+ /* the status of ulpsactivenot1lane D-PHY signal */
+ u32 phy_ulpsactivenot1lane: 1;
+
+ /* the status of ulpsactivenot2lane D-PHY signal */
+ u32 phy_ulpsactivenot2lane: 1;
+
+ /* the status of ulpsactivenot3lane D-PHY signal */
+ u32 phy_ulpsactivenot3lane: 1;
+
+ u32 reserved: 19;
+
+ } bits;
+ } PHY_STATUS;
+
+ union _0xA0 {
+ u32 val;
+ struct _PHY_MIN_STOP_TIME {
+ /* This field configures the minimum wait period to request
+ * a high-speed transmission after the Stop state.
+ */
+ u32 phy_min_stop_time: 8;
+
+ u32 reserved: 24;
+ } bits;
+ } PHY_MIN_STOP_TIME;
+
+ union _0xA4 {
+ u32 val;
+ struct _PHY_LANE_NUM_CONFIG {
+ /*
+ * This field configures the number of active data lanes:
+ * 00: One data lane (lane 0)
+ * 01: Two data lanes (lanes 0 and 1)
+ * 10: Three data lanes (lanes 0, 1, and 2)
+ * 11: Four data lanes (lanes 0, 1, 2, and 3)
+ */
+ u32 phy_lane_num: 2;
+
+ u32 reserved: 30;
+
+ } bits;
+ } PHY_LANE_NUM_CONFIG;
+
+ union _0xA8 {
+ u32 val;
+ struct _PHY_CLKLANE_TIME_CONFIG {
+ /*
+ * This field configures the maximum time that the D-PHY
+ * clock lane takes to go from low-power to high-speed
+ * transmission measured in lane byte clock cycles.
+ */
+ u32 phy_clklane_lp_to_hs_time: 16;
+
+ /*
+ * This field configures the maximum time that the D-PHY
+ * clock lane takes to go from high-speed to low-power
+ * transmission measured in lane byte clock cycles.
+ */
+ u32 phy_clklane_hs_to_lp_time: 16;
+
+ } bits;
+ } PHY_CLKLANE_TIME_CONFIG;
+
+ union _0xAC {
+ u32 val;
+ struct _PHY_DATALANE_TIME_CONFIG {
+ /*
+ * This field configures the maximum time that the D-PHY data
+ * lanes take to go from low-power to high-speed transmission
+ * measured in lane byte clock cycles.
+ */
+ u32 phy_datalane_lp_to_hs_time: 16;
+
+ /*
+ * This field configures the maximum time that the D-PHY data
+ * lanes take to go from high-speed to low-power transmission
+ * measured in lane byte clock cycles.
+ */
+ u32 phy_datalane_hs_to_lp_time: 16;
+
+ } bits;
+ } PHY_DATALANE_TIME_CONFIG;
+
+ union _0xB0 {
+ u32 val;
+ struct _MAX_READ_TIME {
+ /*
+ * This field configures the maximum time required to perform
+ * a read command in lane byte clock cycles. This register can
+ * only be modified when no read command is in progress.
+ */
+ u32 max_rd_time: 16;
+
+ u32 reserved: 16;
+
+ } bits;
+ } MAX_READ_TIME;
+
+ union _0xB4 {
+ u32 val;
+ struct _RX_PKT_CHECK_CONFIG {
+ /* When set to 1, this bit enables the ECC reception, error
+ * correction, and reporting.
+ */
+ u32 rx_pkt_ecc_en: 1;
+
+ /* When set to 1, this bit enables the CRC reception and error
+ * reporting.
+ */
+ u32 rx_pkt_crc_en: 1;
+
+ u32 reserved: 30;
+
+ } bits;
+ } RX_PKT_CHECK_CONFIG;
+
+ union _0xB8 {
+ u32 val;
+ struct _TA_EN {
+ /* When set to 1, this bit enables the Bus Turn-Around (BTA)
+ * request.
+ */
+ u32 ta_en: 1;
+
+ u32 reserved: 31;
+
+ } bits;
+ } TA_EN;
+
+ union _0xBC {
+ u32 val;
+ struct _EOTP_EN {
+ /* When set to 1, this bit enables the EoTp transmission */
+ u32 tx_eotp_en: 1;
+
+ /* When set to 1, this bit enables the EoTp reception. */
+ u32 rx_eotp_en: 1;
+
+ u32 reserved: 30;
+
+ } bits;
+ } EOTP_EN;
+
+ union _0xC0 {
+ u32 val;
+ struct _VIDEO_NULLPKT_SIZE {
+ /*
+ * This register configures the number of bytes inside a null
+ * packet. Setting it to 0 disables the null packets.
+ */
+ u32 video_nullpkt_size: 13;
+
+ u32 reserved: 19;
+
+ } bits;
+ } VIDEO_NULLPKT_SIZE;
+
+ union _0xC4 {
+ u32 val;
+ struct _DCS_WM_PKT_SIZE {
+ /*
+ * This field configures the maximum allowed size for an eDPI
+ * write memory command, measured in pixels. Automatic parti-
+ * tioning of data obtained from eDPI is permanently enabled.
+ */
+ u32 dcs_wm_pkt_size: 16;
+
+ u32 reserved: 16;
+ } bits;
+ } DCS_WM_PKT_SIZE;
+
+ union _0xC8 {
+ u32 val;
+ struct _PROTOCOL_INT_CLR {
+ u32 clr_dphy_errors_0: 1;
+ u32 clr_dphy_errors_1: 1;
+ u32 clr_dphy_errors_2: 1;
+ u32 clr_dphy_errors_3: 1;
+ u32 clr_dphy_errors_4: 1;
+ u32 clr_protocol_debug_err: 11;
+ u32 clr_ack_with_err_0: 1;
+ u32 clr_ack_with_err_1: 1;
+ u32 clr_ack_with_err_2: 1;
+ u32 clr_ack_with_err_3: 1;
+ u32 clr_ack_with_err_4: 1;
+ u32 clr_ack_with_err_5: 1;
+ u32 clr_ack_with_err_6: 1;
+ u32 clr_ack_with_err_7: 1;
+ u32 clr_ack_with_err_8: 1;
+ u32 clr_ack_with_err_9: 1;
+ u32 clr_ack_with_err_10: 1;
+ u32 clr_ack_with_err_11: 1;
+ u32 clr_ack_with_err_12: 1;
+ u32 clr_ack_with_err_13: 1;
+ u32 clr_ack_with_err_14: 1;
+ u32 clr_ack_with_err_15: 1;
+ } bits;
+ } PROTOCOL_INT_CLR;
+
+ union _0xCC {
+ u32 val;
+ struct _INTERNAL_INT_CLR {
+ u32 clr_receive_pkt_size_err: 1;
+ u32 clr_eopt_not_receive_err: 1;
+ u32 clr_gen_cmd_cmd_fifo_wr_err: 1;
+ u32 clr_gen_cmd_rdata_fifo_rd_err: 1;
+ u32 clr_gen_cmd_rdata_fifo_wr_err: 1;
+ u32 clr_gen_cmd_wdata_fifo_wr_err: 1;
+ u32 clr_gen_cmd_wdata_fifo_rd_err: 1;
+ u32 clr_dpi_pix_fifo_wr_err: 1;
+ u32 clr_internal_debug_err: 19;
+ u32 clr_ecc_single_err: 1;
+ u32 clr_ecc_multi_err: 1;
+ u32 clr_crc_err: 1;
+ u32 clr_hs_tx_timeout: 1;
+ u32 clr_lp_rx_timeout: 1;
+ } bits;
+ } INTERNAL_INT_CLR;
+
+ union _0xD0 {
+ u32 val;
+ struct _VIDEO_SIG_DELAY_CONFIG {
+
+ /*
+ * DPI interface signal delay to be used in clk lanebyte
+ * domain for control logic to read video data from pixel
+ * memory in mannal mode, measured in clk_lanebyte cycles
+ */
+ u32 video_sig_delay: 24;
+
+ /*
+ * 1'b1: mannal mode
+ * dsi controller will use video_sig_delay value as
+ * the delay for the packet handle logic to read video
+ * data from pixel memory.
+ *
+ * 1'b0: auto mode
+ * dsi controller will auto calculate the delay for
+ * the packet handle logic to read video data from
+ * pixel memory.
+ */
+ u32 video_sig_delay_mode: 1;
+
+ u32 reserved: 7;
+ } bits;
+ } VIDEO_SIG_DELAY_CONFIG;
+
+ u32 reservedD4_EC[7];
+
+ union _0xF0 {
+ u32 val;
+ struct _PHY_TST_CTRL0 {
+ /* PHY test interface clear (active high) */
+ u32 phy_testclr: 1;
+
+ /* This bit is used to clock the TESTDIN bus into the D-PHY */
+ u32 phy_testclk: 1;
+
+ u32 reserved: 30;
+ } bits;
+ } PHY_TST_CTRL0;
+
+ union _0xF4 {
+ u32 val;
+ struct _PHY_TST_CTRL1 {
+ /* PHY test interface input 8-bit data bus for internal
+ * register programming and test functionalities access.
+ */
+ u32 phy_testdin: 8;
+
+ /* PHY output 8-bit data bus for read-back and internal
+ * probing functionalities.
+ */
+ u32 phy_testdout: 8;
+
+ /*
+ * PHY test interface operation selector:
+ * 1: The address write operation is set on the falling edge
+ * of the testclk signal.
+ * 0: The data write operation is set on the rising edge of
+ * the testclk signal.
+ */
+ u32 phy_testen: 1;
+
+ u32 reserved: 15;
+ } bits;
+ } PHY_TST_CTRL1;
+
+ u32 reservedF8_1FC[66];
+
+ union _0x200 {
+ u32 val;
+ struct _INT_PLL_STS {
+ u32 int_pll_sts: 1;
+ u32 reserved: 31;
+ } bits;
+ } INT_PLL_STS;
+
+ union _0x204 {
+ u32 val;
+ struct _INT_PLL_MSK {
+ u32 int_pll_msk: 1;
+ u32 reserved: 31;
+ } bits;
+ } INT_PLL_MSK;
+
+ union _0x208 {
+ u32 val;
+ struct _INT_PLL_CLR {
+ u32 int_pll_clr: 1;
+ u32 reserved: 31;
+ } bits;
+ } INT_PLL_CLR;
+
+};
+
+#endif
diff --git a/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c b/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c
new file mode 100644
index 0000000..29d818b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "dsi_ctrl_r1p0.h"
+#include "sprd_dphy.h"
+
+/*
+ * Reset D-PHY module
+ * @param phy: pointer to structure
+ * which holds information about the d-phy module
+ * @param reset
+ */
+static void dsi_phy_rstz(struct dphy_context *ctx, int level)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_reset_n = level;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+/*
+ * Power up/down D-PHY module
+ * @param phy: pointer to structure
+ * which holds information about the d-phy module
+ * @param enable (1: shutdown)
+ */
+static void dsi_phy_shutdownz(struct dphy_context *ctx, int level)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_shutdown = level;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+/*
+ * Force D-PHY PLL to stay on while in ULPS
+ * @param phy: pointer to structure
+ * which holds information about the d-phy module
+ * @param force (1) disable (0)
+ * @note To follow the programming model, use wakeup_pll function
+ */
+static void dsi_phy_force_pll(struct dphy_context *ctx, int force)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_force_pll = force;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+static void dsi_phy_clklane_ulps_rqst(struct dphy_context *ctx, int en)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_clk_txrequlps = en;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+static void dsi_phy_clklane_ulps_exit(struct dphy_context *ctx, int en)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_clk_txexitulps = en;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+static void dsi_phy_datalane_ulps_rqst(struct dphy_context *ctx, int en)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_data_txrequlps = en;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+static void dsi_phy_datalane_ulps_exit(struct dphy_context *ctx, int en)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_data_txexitulps = en;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+/*
+ * Configure minimum wait period for HS transmission request after a stop state
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param no_of_byte_cycles [in byte (lane) clock cycles]
+ */
+static void dsi_phy_stop_wait_time(struct dphy_context *ctx, u8 byte_cycle)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+
+ writel(byte_cycle, &reg->PHY_MIN_STOP_TIME);
+}
+
+/*
+ * Set number of active lanes
+ * @param phy: pointer to structure
+ * which holds information about the d-phy module
+ * @param no_of_lanes
+ */
+static void dsi_phy_datalane_en(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+
+ writel(ctx->lanes - 1, &reg->PHY_LANE_NUM_CONFIG);
+}
+
+/*
+ * Enable clock lane module
+ * @param phy pointer to structure
+ * which holds information about the d-phy module
+ * @param en
+ */
+static void dsi_phy_clklane_en(struct dphy_context *ctx, int en)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x78 phy_interface_ctrl;
+
+ phy_interface_ctrl.val = readl(&reg->PHY_INTERFACE_CTRL);
+ phy_interface_ctrl.bits.rf_phy_clk_en = en;
+
+ writel(phy_interface_ctrl.val, &reg->PHY_INTERFACE_CTRL);
+}
+
+/*
+ * Request the PHY module to start transmission of high speed clock.
+ * This causes the clock lane to start transmitting DDR clock on the
+ * lane interconnect.
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param enable
+ * @note this function should be called explicitly by user always except for
+ * transmitting
+ */
+static void dsi_phy_clk_hs_rqst(struct dphy_context *ctx, int enable)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x74 phy_clk_lane_lp_ctrl;
+
+ phy_clk_lane_lp_ctrl.val = readl(&reg->PHY_CLK_LANE_LP_CTRL);
+ phy_clk_lane_lp_ctrl.bits.auto_clklane_ctrl_en = 0;
+ phy_clk_lane_lp_ctrl.bits.phy_clklane_tx_req_hs = enable;
+
+ writel(phy_clk_lane_lp_ctrl.val, &reg->PHY_CLK_LANE_LP_CTRL);
+}
+
+/*
+ * Get D-PHY PPI status
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param mask
+ * @return status
+ */
+static u8 dsi_phy_is_pll_locked(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ return phy_status.bits.phy_lock;
+}
+
+static u8 dsi_phy_is_rx_direction(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ return phy_status.bits.phy_direction;
+}
+
+static u8 dsi_phy_is_rx_ulps_esc_lane0(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ return phy_status.bits.phy_rxulpsesc0lane;
+}
+
+static u8 dsi_phy_is_stop_state_datalane(struct dphy_context *ctx)
+{
+ u8 state = 0;
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ if (phy_status.bits.phy_stopstate0lane)
+ state |= BIT(0);
+ if (phy_status.bits.phy_stopstate1lane)
+ state |= BIT(1);
+ if (phy_status.bits.phy_stopstate2lane)
+ state |= BIT(2);
+ if (phy_status.bits.phy_stopstate3lane)
+ state |= BIT(3);
+
+ return state;
+}
+
+static u8 dsi_phy_is_stop_state_clklane(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ return phy_status.bits.phy_stopstateclklane;
+}
+
+static u8 dsi_phy_is_ulps_active_datalane(struct dphy_context *ctx)
+{
+ u8 state = 0;
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ if (!phy_status.bits.phy_ulpsactivenot0lane)
+ state |= BIT(0);
+ if (!phy_status.bits.phy_ulpsactivenot1lane)
+ state |= BIT(1);
+ if (!phy_status.bits.phy_ulpsactivenot2lane)
+ state |= BIT(2);
+ if (!phy_status.bits.phy_ulpsactivenot3lane)
+ state |= BIT(3);
+
+ return state;
+}
+
+static u8 dsi_phy_is_ulps_active_clklane(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0x9C phy_status;
+
+ phy_status.val = readl(&reg->PHY_STATUS);
+
+ return !phy_status.bits.phy_ulpsactivenotclk;
+}
+
+/*
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param value
+ */
+static void dsi_phy_test_clk(struct dphy_context *ctx, u8 value)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0xF0 phy_tst_ctrl0;
+
+ phy_tst_ctrl0.val = readl(&reg->PHY_TST_CTRL0);
+ phy_tst_ctrl0.bits.phy_testclk = value;
+
+ writel(phy_tst_ctrl0.val, &reg->PHY_TST_CTRL0);
+}
+
+/*
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param value
+ */
+static void dsi_phy_test_clr(struct dphy_context *ctx, u8 value)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0xF0 phy_tst_ctrl0;
+
+ phy_tst_ctrl0.val = readl(&reg->PHY_TST_CTRL0);
+ phy_tst_ctrl0.bits.phy_testclr = value;
+
+ writel(phy_tst_ctrl0.val, &reg->PHY_TST_CTRL0);
+}
+
+/*
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param on_falling_edge
+ */
+static void dsi_phy_test_en(struct dphy_context *ctx, u8 value)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0xF4 phy_tst_ctrl1;
+
+ phy_tst_ctrl1.val = readl(&reg->PHY_TST_CTRL1);
+ phy_tst_ctrl1.bits.phy_testen = value;
+
+ writel(phy_tst_ctrl1.val, &reg->PHY_TST_CTRL1);
+}
+
+/*
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ */
+static u8 dsi_phy_test_dout(struct dphy_context *ctx)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0xF4 phy_tst_ctrl1;
+
+ phy_tst_ctrl1.val = readl(&reg->PHY_TST_CTRL1);
+
+ return phy_tst_ctrl1.bits.phy_testdout;
+}
+
+/*
+ * @param phy pointer to structure which holds information about the d-phy
+ * module
+ * @param test_data
+ */
+static void dsi_phy_test_din(struct dphy_context *ctx, u8 data)
+{
+ struct dsi_reg *reg = (struct dsi_reg *)ctx->ctrlbase;
+ union _0xF4 phy_tst_ctrl1;
+
+ phy_tst_ctrl1.val = readl(&reg->PHY_TST_CTRL1);
+ phy_tst_ctrl1.bits.phy_testdin = data;
+
+ writel(phy_tst_ctrl1.val, &reg->PHY_TST_CTRL1);
+}
+
+static struct dphy_ppi_ops dsi_ctrl_ppi_ops = {
+ .rstz = dsi_phy_rstz,
+ .shutdownz = dsi_phy_shutdownz,
+ .force_pll = dsi_phy_force_pll,
+ .clklane_ulps_rqst = dsi_phy_clklane_ulps_rqst,
+ .clklane_ulps_exit = dsi_phy_clklane_ulps_exit,
+ .datalane_ulps_rqst = dsi_phy_datalane_ulps_rqst,
+ .datalane_ulps_exit = dsi_phy_datalane_ulps_exit,
+ .stop_wait_time = dsi_phy_stop_wait_time,
+ .datalane_en = dsi_phy_datalane_en,
+ .clklane_en = dsi_phy_clklane_en,
+ .clk_hs_rqst = dsi_phy_clk_hs_rqst,
+ .is_pll_locked = dsi_phy_is_pll_locked,
+ .is_rx_direction = dsi_phy_is_rx_direction,
+ .is_rx_ulps_esc_lane0 = dsi_phy_is_rx_ulps_esc_lane0,
+ .is_stop_state_datalane = dsi_phy_is_stop_state_datalane,
+ .is_stop_state_clklane = dsi_phy_is_stop_state_clklane,
+ .is_ulps_active_datalane = dsi_phy_is_ulps_active_datalane,
+ .is_ulps_active_clklane = dsi_phy_is_ulps_active_clklane,
+ .tst_clk = dsi_phy_test_clk,
+ .tst_clr = dsi_phy_test_clr,
+ .tst_en = dsi_phy_test_en,
+ .tst_dout = dsi_phy_test_dout,
+ .tst_din = dsi_phy_test_din,
+ .bist_en = NULL,
+ .is_bist_ok = NULL,
+};
+
+static struct ops_entry entry = {
+ .ver = "sprd,dsi-ctrl",
+ .ops = &dsi_ctrl_ppi_ops,
+};
+
+static int __init dphy_ppi_register(void)
+{
+ return dphy_ppi_ops_register(&entry);
+}
+
+subsys_initcall(dphy_ppi_register);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("DPHY Protocal Interface for Unisoc DSI_CTRL RXP0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c b/drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c
new file mode 100644
index 0000000..b17324b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/delay.h>
+
+#include "sprd_dsi_hal.h"
+
+static u16 calc_bytes_per_pixel_x100(int coding)
+{
+ u16 Bpp_x100;
+
+ switch (coding) {
+ case COLOR_CODE_16BIT_CONFIG1:
+ case COLOR_CODE_16BIT_CONFIG2:
+ case COLOR_CODE_16BIT_CONFIG3:
+ Bpp_x100 = 200;
+ break;
+ case COLOR_CODE_18BIT_CONFIG1:
+ case COLOR_CODE_18BIT_CONFIG2:
+ Bpp_x100 = 225;
+ break;
+ case COLOR_CODE_24BIT:
+ Bpp_x100 = 300;
+ break;
+ case COLOR_CODE_COMPRESSTION:
+ Bpp_x100 = 100;
+ break;
+ case COLOR_CODE_20BIT_YCC422_LOOSELY:
+ Bpp_x100 = 250;
+ break;
+ case COLOR_CODE_24BIT_YCC422:
+ Bpp_x100 = 300;
+ break;
+ case COLOR_CODE_16BIT_YCC422:
+ Bpp_x100 = 200;
+ break;
+ case COLOR_CODE_30BIT:
+ Bpp_x100 = 375;
+ break;
+ case COLOR_CODE_36BIT:
+ Bpp_x100 = 450;
+ break;
+ case COLOR_CODE_12BIT_YCC420:
+ Bpp_x100 = 150;
+ break;
+ default:
+ pr_err("invalid color coding");
+ Bpp_x100 = 0;
+ break;
+ }
+
+ return Bpp_x100;
+}
+
+static u8 calc_video_size_step(int coding)
+{
+ u8 video_size_step;
+
+ switch (coding) {
+ case COLOR_CODE_16BIT_CONFIG1:
+ case COLOR_CODE_16BIT_CONFIG2:
+ case COLOR_CODE_16BIT_CONFIG3:
+ case COLOR_CODE_18BIT_CONFIG1:
+ case COLOR_CODE_18BIT_CONFIG2:
+ case COLOR_CODE_24BIT:
+ case COLOR_CODE_COMPRESSTION:
+ return video_size_step = 1;
+ case COLOR_CODE_20BIT_YCC422_LOOSELY:
+ case COLOR_CODE_24BIT_YCC422:
+ case COLOR_CODE_16BIT_YCC422:
+ case COLOR_CODE_30BIT:
+ case COLOR_CODE_36BIT:
+ case COLOR_CODE_12BIT_YCC420:
+ return video_size_step = 2;
+ default:
+ pr_err("invalid color coding");
+ return 0;
+ }
+}
+
+static u16 round_video_size(int coding, u16 video_size)
+{
+ switch (coding) {
+ case COLOR_CODE_16BIT_YCC422:
+ case COLOR_CODE_24BIT_YCC422:
+ case COLOR_CODE_20BIT_YCC422_LOOSELY:
+ case COLOR_CODE_12BIT_YCC420:
+ /* round up active H pixels to a multiple of 2 */
+ if ((video_size % 2) != 0)
+ video_size += 1;
+ break;
+ default:
+ break;
+ }
+
+ return video_size;
+}
+
+#define SPRD_MIPI_DSI_FMT_DSC 0xff
+static u32 fmt_to_coding(u32 fmt)
+{
+ switch (fmt) {
+ case MIPI_DSI_FMT_RGB565:
+ return COLOR_CODE_16BIT_CONFIG1;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ return COLOR_CODE_18BIT_CONFIG1;
+ case MIPI_DSI_FMT_RGB666:
+ case MIPI_DSI_FMT_RGB888:
+ return COLOR_CODE_24BIT;
+ case SPRD_MIPI_DSI_FMT_DSC:
+ return COLOR_CODE_COMPRESSTION;
+ default:
+ DRM_ERROR("Unsupported format (%d)\n", fmt);
+ return COLOR_CODE_24BIT;
+ }
+}
+
+#define ns_to_cycle(ns, byte_clk) \
+ DIV_ROUND_UP((ns) * (byte_clk), 1000000)
+
+int sprd_dsi_init(struct sprd_dsi *dsi)
+{
+ int div;
+ struct dsi_context *ctx = &dsi->ctx;
+ u16 max_rd_time;
+ u16 data_hs2lp, data_lp2hs, clk_hs2lp, clk_lp2hs;
+
+ dsi_hal_power_en(dsi, 0);
+ dsi_hal_int0_mask(dsi, 0xffffffff);
+ dsi_hal_int1_mask(dsi, 0xffffffff);
+ dsi_hal_cmd_mode(dsi);
+ dsi_hal_eotp_rx_en(dsi, 0);
+ dsi_hal_eotp_tx_en(dsi, 0);
+ dsi_hal_ecc_rx_en(dsi, 1);
+ dsi_hal_crc_rx_en(dsi, 1);
+ dsi_hal_bta_en(dsi, 1);
+ dsi_hal_video_vcid(dsi, 0);
+ dsi_hal_rx_vcid(dsi, 0);
+
+ div = DIV_ROUND_UP(ctx->byte_clk, ctx->esc_clk);
+ dsi_hal_tx_escape_division(dsi, div);
+ pr_info("escape clock divider = %d\n", div);
+
+ max_rd_time = ns_to_cycle(ctx->max_rd_time, ctx->byte_clk);
+ dsi_hal_max_read_time(dsi, max_rd_time);
+
+ data_hs2lp = ns_to_cycle(ctx->data_hs2lp, ctx->byte_clk);
+ data_lp2hs = ns_to_cycle(ctx->data_lp2hs, ctx->byte_clk);
+ clk_hs2lp = ns_to_cycle(ctx->clk_hs2lp, ctx->byte_clk);
+ clk_lp2hs = ns_to_cycle(ctx->clk_lp2hs, ctx->byte_clk);
+ dsi_hal_datalane_hs2lp_config(dsi, data_hs2lp);
+ dsi_hal_datalane_lp2hs_config(dsi, data_lp2hs);
+ dsi_hal_clklane_hs2lp_config(dsi, clk_hs2lp);
+ dsi_hal_clklane_lp2hs_config(dsi, clk_lp2hs);
+
+ dsi_hal_power_en(dsi, 1);
+
+ return 0;
+}
+
+/*
+ * Close DSI Host driver
+ * - Free up resources and shutdown host controller and PHY
+ * @param dsi pointer to structure holding the DSI Host core information
+ * @return int
+ */
+int sprd_dsi_uninit(struct sprd_dsi *dsi)
+{
+ dsi_hal_int0_mask(dsi, 0xffffffff);
+ dsi_hal_int1_mask(dsi, 0xffffffff);
+ dsi_hal_power_en(dsi, 0);
+
+ return 0;
+}
+
+/*
+ * Configure DPI video interface
+ * - If not in burst mode, it will compute the video and null packet sizes
+ * according to necessity
+ * - Configure timers for data lanes and/or clock lane to return to LP when
+ * bandwidth is not filled by data
+ * @param dsi pointer to structure holding the DSI Host core information
+ * @param param pointer to video stream-to-send information
+ * @return error code
+ */
+int sprd_dsi_dpi_video(struct sprd_dsi *dsi)
+{
+ u16 Bpp_x100;
+ u16 video_size;
+ u32 ratio_x1000;
+ u16 null_pkt_size = 0;
+ u8 video_size_step;
+ u32 hs_to;
+ u32 total_bytes;
+ u32 bytes_per_chunk;
+ u32 chunks = 0;
+ u32 bytes_left = 0;
+ u32 chunk_overhead;
+ const u8 pkt_header = 6;
+ u8 coding;
+ int div;
+ u16 hline;
+ struct dsi_context *ctx = &dsi->ctx;
+ struct videomode *vm = &dsi->ctx.vm;
+
+ coding = fmt_to_coding(ctx->format);
+ video_size = round_video_size(coding, vm->hactive);
+ Bpp_x100 = calc_bytes_per_pixel_x100(coding);
+ video_size_step = calc_video_size_step(coding);
+ ratio_x1000 = ctx->byte_clk * 1000 / (vm->pixelclock / 1000);
+ hline = vm->hactive + vm->hsync_len + vm->hfront_porch +
+ vm->hback_porch;
+
+ dsi_hal_power_en(dsi, 0);
+ dsi_hal_dpi_frame_ack_en(dsi, ctx->frame_ack_en);
+ dsi_hal_dpi_color_coding(dsi, coding);
+ dsi_hal_dpi_video_burst_mode(dsi, ctx->burst_mode);
+ dsi_hal_dpi_sig_delay(dsi, 95 * hline * ratio_x1000 / 100000);
+ dsi_hal_dpi_hline_time(dsi, hline * ratio_x1000 / 1000);
+ dsi_hal_dpi_hsync_time(dsi, vm->hsync_len * ratio_x1000 / 1000);
+ dsi_hal_dpi_hbp_time(dsi, vm->hback_porch * ratio_x1000 / 1000);
+ dsi_hal_dpi_vact(dsi, vm->vactive);
+ dsi_hal_dpi_vfp(dsi, vm->vfront_porch);
+ dsi_hal_dpi_vbp(dsi, vm->vback_porch);
+ dsi_hal_dpi_vsync(dsi, vm->vsync_len);
+ dsi_hal_dpi_hporch_lp_en(dsi, 1);
+ dsi_hal_dpi_vporch_lp_en(dsi, 1);
+ dsi_hal_dpi_hsync_pol(dsi, 0);
+ dsi_hal_dpi_vsync_pol(dsi, 0);
+ dsi_hal_dpi_data_en_pol(dsi, 0);
+ dsi_hal_dpi_color_mode_pol(dsi, 0);
+ dsi_hal_dpi_shut_down_pol(dsi, 0);
+
+ hs_to = (hline * vm->vactive) + (2 * Bpp_x100) / 100;
+ for (div = 0x80; (div < hs_to) && (div > 2); div--) {
+ if ((hs_to % div) == 0) {
+ dsi_hal_timeout_clock_division(dsi, div);
+ dsi_hal_lp_rx_timeout(dsi, hs_to / div);
+ dsi_hal_hs_tx_timeout(dsi, hs_to / div);
+ break;
+ }
+ }
+
+ if (ctx->burst_mode == VIDEO_BURST_WITH_SYNC_PULSES) {
+ dsi_hal_dpi_video_packet_size(dsi, video_size);
+ dsi_hal_dpi_null_packet_size(dsi, 0);
+ dsi_hal_dpi_chunk_num(dsi, 0);
+ } else {
+ /* non burst transmission */
+ null_pkt_size = 0;
+
+ /* bytes to be sent - first as one chunk */
+ bytes_per_chunk = vm->hactive * Bpp_x100 / 100 + pkt_header;
+
+ /* hline total bytes from the DPI interface */
+ total_bytes = (vm->hactive + vm->hfront_porch) *
+ ratio_x1000 / ctx->lanes / 1000;
+
+ /* check if the pixels actually fit on the DSI link */
+ if (total_bytes < bytes_per_chunk) {
+ pr_err("current resolution can not be set\n");
+ return -1;
+ }
+
+ chunk_overhead = total_bytes - bytes_per_chunk;
+
+ /* overhead higher than 1 -> enable multi packets */
+ if (chunk_overhead > 1) {
+
+ /* multi packets */
+ for (video_size = video_size_step;
+ video_size < vm->hactive;
+ video_size += video_size_step) {
+
+ if (vm->hactive * 1000 / video_size % 1000)
+ continue;
+
+ chunks = vm->hactive / video_size;
+ bytes_per_chunk = Bpp_x100 * video_size / 100
+ + pkt_header;
+ if (total_bytes >= (bytes_per_chunk * chunks)) {
+ bytes_left = total_bytes -
+ bytes_per_chunk * chunks;
+ break;
+ }
+ }
+
+ /* prevent overflow (unsigned - unsigned) */
+ if (bytes_left > (pkt_header * chunks)) {
+ null_pkt_size = (bytes_left -
+ pkt_header * chunks) / chunks;
+ /* avoid register overflow */
+ if (null_pkt_size > 1023)
+ null_pkt_size = 1023;
+ }
+
+ } else {
+
+ /* single packet */
+ chunks = 1;
+
+ /* must be a multiple of 4 except 18 loosely */
+ for (video_size = vm->hactive;
+ (video_size % video_size_step) != 0;
+ video_size++)
+ ;
+ }
+
+ dsi_hal_dpi_video_packet_size(dsi, video_size);
+ dsi_hal_dpi_null_packet_size(dsi, null_pkt_size);
+ dsi_hal_dpi_chunk_num(dsi, chunks);
+ }
+
+ dsi_hal_int0_mask(dsi, ctx->int0_mask);
+ dsi_hal_int1_mask(dsi, ctx->int1_mask);
+ dsi_hal_power_en(dsi, 1);
+
+ return 0;
+}
+
+int sprd_dsi_edpi_video(struct sprd_dsi *dsi)
+{
+ const u32 fifo_depth = 1096;
+ const u32 word_length = 4;
+ struct dsi_context *ctx = &dsi->ctx;
+ u32 hactive = ctx->vm.hactive;
+ u32 Bpp_x100;
+ u32 max_fifo_len;
+ u8 coding;
+
+ coding = fmt_to_coding(ctx->format);
+ Bpp_x100 = calc_bytes_per_pixel_x100(coding);
+ max_fifo_len = word_length * fifo_depth * 100 / Bpp_x100;
+
+ dsi_hal_power_en(dsi, 0);
+ dsi_hal_dpi_color_coding(dsi, coding);
+ dsi_hal_tear_effect_ack_en(dsi, ctx->te_ack_en);
+
+ if (max_fifo_len > hactive)
+ dsi_hal_edpi_max_pkt_size(dsi, hactive);
+ else
+ dsi_hal_edpi_max_pkt_size(dsi, max_fifo_len);
+
+ dsi_hal_int0_mask(dsi, ctx->int0_mask);
+ dsi_hal_int1_mask(dsi, ctx->int1_mask);
+ dsi_hal_power_en(dsi, 1);
+
+ return 0;
+}
+
+/*
+ * Send a packet on the generic interface
+ * @param dsi pointer to structure holding the DSI Host core information
+ * @param vc destination virtual channel
+ * @param data_type type of command, inserted in first byte of header
+ * @param params byte array of command parameters
+ * @param param_length length of the above array
+ * @return error code
+ * @note this function has an active delay to wait for the buffer to clear.
+ * The delay is limited to:
+ * (param_length / 4) x DSIH_FIFO_ACTIVE_WAIT x register access time
+ * @note the controller restricts the sending of .
+ * This function will not be able to send Null and Blanking packets due to
+ * controller restriction
+ */
+int sprd_dsi_wr_pkt(struct sprd_dsi *dsi, u8 vc, u8 type,
+ const u8 *param, u16 len)
+{
+ int i = 0;
+ int j = 0;
+ u32 payload;
+ u8 wc_lsbyte, wc_msbyte;
+
+ if (vc > 3)
+ return -EINVAL;
+
+ /* 1st: for long packet, must config payload first */
+ if (!dsi_hal_wait_tx_payload_fifo_empty(dsi))
+ return -1;
+
+ if (len > 2) {
+ for (i = 0; i < len; i += j) {
+ payload = 0;
+ for (j = 0; (j < 4) && ((j + i) < (len)); j++)
+ payload |= param[i + j] << (j * 8);
+
+ dsi_hal_set_packet_payload(dsi, payload);
+ }
+ wc_lsbyte = len & 0xff;
+ wc_msbyte = len >> 8;
+ } else {
+ wc_lsbyte = (len > 0) ? param[0] : 0;
+ wc_msbyte = (len > 1) ? param[1] : 0;
+ }
+
+ /* 2nd: then set packet header */
+ if (!dsi_hal_wait_tx_cmd_fifo_empty(dsi))
+ return -EINVAL;
+
+ dsi_hal_set_packet_header(dsi, vc, type, wc_lsbyte, wc_msbyte);
+
+ return 0;
+}
+
+
+/*
+ * Send READ packet to peripheral using the generic interface
+ * This will force command mode and stop video mode (because of BTA)
+ * @param dsi pointer to structure holding the DSI Host core information
+ * @param vc destination virtual channel
+ * @param data_type generic command type
+ * @param lsb_byte first command parameter
+ * @param msb_byte second command parameter
+ * @param bytes_to_read no of bytes to read (expected to arrive at buffer)
+ * @param read_buffer pointer to 8-bit array to hold the read buffer words
+ * return read_buffer_length
+ * @note this function has an active delay to wait for the buffer to clear.
+ * The delay is limited to 2 x DSIH_FIFO_ACTIVE_WAIT
+ * (waiting for command buffer, and waiting for receiving)
+ * @note this function will enable BTA
+ */
+int sprd_dsi_rd_pkt(struct sprd_dsi *dsi, u8 vc, u8 type,
+ u8 msb_byte, u8 lsb_byte,
+ u8 *buffer, u8 bytes_to_read)
+{
+ int i;
+ int count = 0;
+ u32 temp;
+
+ if (vc > 3)
+ return -EINVAL;
+
+ /* 1st: send read command to peripheral */
+ if (!dsi_hal_wait_tx_cmd_fifo_empty(dsi))
+ return -EINVAL;
+
+ dsi_hal_set_packet_header(dsi, vc, type, lsb_byte, msb_byte);
+
+ /* 2nd: wait peripheral response completed */
+ dsi_hal_wait_rd_resp_completed(dsi);
+
+ /* 3rd: get data from rx payload fifo */
+ if (dsi_hal_is_rx_payload_fifo_empty(dsi)) {
+ pr_err("rx payload fifo empty\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 100; i++) {
+ temp = dsi_hal_get_rx_payload(dsi);
+
+ if (count < bytes_to_read)
+ buffer[count++] = temp & 0xff;
+ if (count < bytes_to_read)
+ buffer[count++] = (temp >> 8) & 0xff;
+ if (count < bytes_to_read)
+ buffer[count++] = (temp >> 16) & 0xff;
+ if (count < bytes_to_read)
+ buffer[count++] = (temp >> 24) & 0xff;
+
+ if (dsi_hal_is_rx_payload_fifo_empty(dsi))
+ return count;
+ }
+
+ pr_err("read too many buffers\n");
+ return -EINVAL;
+}
+
+void sprd_dsi_set_work_mode(struct sprd_dsi *dsi, u8 mode)
+{
+ if (mode == DSI_MODE_CMD)
+ dsi_hal_cmd_mode(dsi);
+ else
+ dsi_hal_video_mode(dsi);
+}
+
+int sprd_dsi_get_work_mode(struct sprd_dsi *dsi)
+{
+ if (dsi_hal_is_cmd_mode(dsi))
+ return DSI_MODE_CMD;
+ else
+ return DSI_MODE_VIDEO;
+}
+
+void sprd_dsi_lp_cmd_enable(struct sprd_dsi *dsi, bool enable)
+{
+ if (dsi_hal_is_cmd_mode(dsi))
+ dsi_hal_cmd_mode_lp_cmd_en(dsi, enable);
+ else
+ dsi_hal_video_mode_lp_cmd_en(dsi, enable);
+}
+
+void sprd_dsi_nc_clk_en(struct sprd_dsi *dsi, bool enable)
+{
+ dsi_hal_nc_clk_en(dsi, enable);
+}
+
+void sprd_dsi_state_reset(struct sprd_dsi *dsi)
+{
+ dsi_hal_power_en(dsi, 0);
+ udelay(100);
+ dsi_hal_power_en(dsi, 1);
+}
+
+u32 sprd_dsi_int_status(struct sprd_dsi *dsi, int index)
+{
+ u32 status = 0;
+
+ switch (index) {
+ case 0:
+ status = dsi_hal_int0_status(dsi);
+ break;
+
+ case 1:
+ status = dsi_hal_int1_status(dsi);
+ break;
+
+ case 2:
+ status = dsi_hal_int2_status(dsi);
+ break;
+
+ default:
+ break;
+ }
+
+ return status;
+}
+
+void sprd_dsi_int_mask(struct sprd_dsi *dsi, int index)
+{
+ switch (index) {
+ case 0:
+ dsi_hal_int0_mask(dsi, dsi->ctx.int0_mask);
+ break;
+
+ case 1:
+ dsi_hal_int1_mask(dsi, dsi->ctx.int1_mask);
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h b/drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h
new file mode 100644
index 0000000..45a706a
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _MIPI_DSI_API_H_
+#define _MIPI_DSI_API_H_
+
+#include "sprd_dsi.h"
+
+int sprd_dsi_init(struct sprd_dsi *dsi);
+int sprd_dsi_uninit(struct sprd_dsi *dsi);
+int sprd_dsi_dpi_video(struct sprd_dsi *dsi);
+int sprd_dsi_edpi_video(struct sprd_dsi *dsi);
+int sprd_dsi_wr_pkt(struct sprd_dsi *dsi, u8 vc, u8 type,
+ const u8 *param, u16 len);
+int sprd_dsi_rd_pkt(struct sprd_dsi *dsi, u8 vc, u8 type,
+ u8 msb_byte, u8 lsb_byte,
+ u8 *buffer, u8 bytes_to_read);
+void sprd_dsi_set_work_mode(struct sprd_dsi *dsi, u8 mode);
+int sprd_dsi_get_work_mode(struct sprd_dsi *dsi);
+void sprd_dsi_lp_cmd_enable(struct sprd_dsi *dsi, bool enable);
+void sprd_dsi_nc_clk_en(struct sprd_dsi *dsi, bool enable);
+void sprd_dsi_state_reset(struct sprd_dsi *dsi);
+u32 sprd_dsi_int_status(struct sprd_dsi *dsi, int index);
+void sprd_dsi_int_mask(struct sprd_dsi *dsi, int index);
+
+#endif /* _MIPI_DSI_API_H_ */
diff --git a/drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h b/drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h
new file mode 100644
index 0000000..666500d
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h
@@ -0,0 +1,1102 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef MIPI_DSI_HAL_H_
+#define MIPI_DSI_HAL_H_
+
+#include <linux/delay.h>
+#include <linux/types.h>
+
+#include "sprd_dsi.h"
+
+/*
+ * Get DSI Host core version
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return true or false
+ */
+static inline bool dsi_hal_check_version(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (!ops->check_version)
+ return false;
+
+ return ops->check_version(ctx);
+}
+/*
+ * Modify power status of DSI Host core
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable power on(1) or off (0)
+ */
+static inline void dsi_hal_power_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->power_en)
+ ops->power_en(ctx, enable);
+}
+/*
+ * Enable DPI video mode
+ * @dsi pointer to structure holding the DSI Host core information
+ */
+static inline void dsi_hal_video_mode(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->video_mode)
+ ops->video_mode(ctx);
+}
+/*
+ * Enable command mode (Generic interface)
+ * @dsi pointer to structure holding the DSI Host core information
+ */
+static inline void dsi_hal_cmd_mode(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->cmd_mode)
+ ops->cmd_mode(ctx);
+}
+static inline bool dsi_hal_is_cmd_mode(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_cmd_mode)
+ return ops->is_cmd_mode(ctx);
+
+ return true;
+}
+/*
+ * Configure the read back virtual channel for the generic interface
+ * @dsi pointer to structure holding the DSI Host core information
+ * @vc to listen to on the line
+ */
+static inline void dsi_hal_rx_vcid(struct sprd_dsi *dsi, u8 vc)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->rx_vcid)
+ ops->rx_vcid(ctx, vc);
+}
+/*
+ * Write the DPI video virtual channel destination
+ * @dsi pointer to structure holding the DSI Host core information
+ * @vc virtual channel
+ */
+static inline void dsi_hal_video_vcid(struct sprd_dsi *dsi, u8 vc)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->video_vcid)
+ ops->video_vcid(ctx, vc);
+}
+/*
+ * Set DPI video mode type (burst/non-burst - with sync pulses or events)
+ * @dsi pointer to structure holding the DSI Host core information
+ * @type burst mode type: with sync pulse or events
+ */
+static inline
+void dsi_hal_dpi_video_burst_mode(struct sprd_dsi *dsi, int type)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_video_burst_mode)
+ ops->dpi_video_burst_mode(ctx, type);
+}
+/*
+ * Set DPI video color coding
+ * @dsi pointer to structure holding the DSI Host core information
+ * @coding enum (configuration and color depth)
+ */
+static inline void dsi_hal_dpi_color_coding(struct sprd_dsi *dsi, int coding)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_color_coding)
+ ops->dpi_color_coding(ctx, coding);
+}
+/*
+ * Set DPI loosely packetisation video (used only when color depth = 18
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable 1 enable, 0 disable
+ */
+static inline
+void dsi_hal_dpi_18_loosely_packet_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_18_loosely_packet_en)
+ ops->dpi_18_loosely_packet_en(ctx, enable);
+}
+/*
+ * Set DPI color mode pin polarity
+ * @dsi pointer to structure holding the DSI Host core information
+ * @active_low (1) or active high (0)
+ */
+static inline
+void dsi_hal_dpi_color_mode_pol(struct sprd_dsi *dsi, int active_low)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_color_mode_pol)
+ ops->dpi_color_mode_pol(ctx, active_low);
+}
+/*
+ * Set DPI shut down pin polarity
+ * @dsi pointer to structure holding the DSI Host core information
+ * @active_low (1) or active high (0)
+ */
+static inline
+void dsi_hal_dpi_shut_down_pol(struct sprd_dsi *dsi, int active_low)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_shut_down_pol)
+ ops->dpi_shut_down_pol(ctx, active_low);
+}
+/*
+ * Set DPI horizontal sync pin polarity
+ * @dsi pointer to structure holding the DSI Host core information
+ * @active_low (1) or active high (0)
+ */
+static inline
+void dsi_hal_dpi_hsync_pol(struct sprd_dsi *dsi, int active_low)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_hsync_pol)
+ ops->dpi_hsync_pol(ctx, active_low);
+}
+/*
+ * Set DPI vertical sync pin polarity
+ * @dsi pointer to structure holding the DSI Host core information
+ * @active_low (1) or active high (0)
+ */
+static inline
+void dsi_hal_dpi_vsync_pol(struct sprd_dsi *dsi, int active_low)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_vsync_pol)
+ ops->dpi_vsync_pol(ctx, active_low);
+}
+/*
+ * Set DPI data enable pin polarity
+ * @dsi pointer to structure holding the DSI Host core information
+ * @active_low (1) or active high (0)
+ */
+static inline
+void dsi_hal_dpi_data_en_pol(struct sprd_dsi *dsi, int active_low)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_data_en_pol)
+ ops->dpi_data_en_pol(ctx, active_low);
+}
+/*
+ * Configure the Horizontal Line time
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle taken to transmit the total of the horizontal line
+ */
+static inline
+void dsi_hal_dpi_hline_time(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_hline_time)
+ ops->dpi_hline_time(ctx, byte_cycle);
+}
+/*
+ * Configure the Horizontal back porch time
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle taken to transmit the horizontal back porch
+ */
+static inline
+void dsi_hal_dpi_hbp_time(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_hbp_time)
+ ops->dpi_hbp_time(ctx, byte_cycle);
+}
+/*
+ * Configure the Horizontal sync time
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle taken to transmit the horizontal sync
+ */
+static inline
+void dsi_hal_dpi_hsync_time(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_hsync_time)
+ ops->dpi_hsync_time(ctx, byte_cycle);
+}
+/*
+ * Configure the DPI Interface signal delay time
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle delay time after receiving HSYNC from DPI interface
+ * to retrieve data from pixel memory
+ */
+static inline
+void dsi_hal_dpi_sig_delay(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_sig_delay)
+ ops->dpi_sig_delay(ctx, byte_cycle);
+}
+/*
+ * Configure the vertical active lines of the video stream
+ * @dsi pointer to structure holding the DSI Host core information
+ * @lines
+ */
+static inline void dsi_hal_dpi_vact(struct sprd_dsi *dsi, u16 lines)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_vact)
+ ops->dpi_vact(ctx, lines);
+}
+/*
+ * Configure the vertical front porch lines of the video stream
+ * @dsi pointer to structure holding the DSI Host core information
+ * @lines
+ */
+static inline void dsi_hal_dpi_vfp(struct sprd_dsi *dsi, u16 lines)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_vfp)
+ ops->dpi_vfp(ctx, lines);
+}
+/*
+ * Configure the vertical back porch lines of the video stream
+ * @dsi pointer to structure holding the DSI Host core information
+ * @lines
+ */
+static inline void dsi_hal_dpi_vbp(struct sprd_dsi *dsi, u16 lines)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_vbp)
+ ops->dpi_vbp(ctx, lines);
+}
+/*
+ * Configure the vertical sync lines of the video stream
+ * @dsi pointer to structure holding the DSI Host core information
+ * @lines
+ */
+static inline void dsi_hal_dpi_vsync(struct sprd_dsi *dsi, u16 lines)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_vsync)
+ ops->dpi_vsync(ctx, lines);
+}
+/*
+ * Enable return to low power mode inside horizontal front porch periods when
+ * timing allows
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ */
+static inline
+void dsi_hal_dpi_hporch_lp_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_hporch_lp_en)
+ ops->dpi_hporch_lp_en(ctx, enable);
+}
+/*
+ * Enable return to low power mode inside vertical active lines periods when
+ * timing allows
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ */
+static inline
+void dsi_hal_dpi_vporch_lp_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_vporch_lp_en)
+ ops->dpi_vporch_lp_en(ctx, enable);
+}
+/*
+ * Enable FRAME BTA ACK
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ */
+static inline
+void dsi_hal_dpi_frame_ack_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_frame_ack_en)
+ ops->dpi_frame_ack_en(ctx, enable);
+}
+/*
+ * Enable null packets (value in null packet size will be taken in calculations)
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ * note: function retained for backward compatibility (not used from 1.20a on)
+ */
+static inline
+void dsi_hal_dpi_null_packet_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_null_packet_en)
+ ops->dpi_null_packet_en(ctx, enable);
+}
+/*
+ * Enable multi packets (value in no of chunks will be taken in calculations)
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ */
+static inline
+void dsi_hal_dpi_multi_packet_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_multi_packet_en)
+ ops->dpi_multi_packet_en(ctx, enable);
+}
+/*
+ * Write no of chunks to core - taken into consideration only when multi packet
+ * is enabled
+ * @dsi pointer to structure holding the DSI Host core information
+ * @num number of chunks
+ */
+static inline void dsi_hal_dpi_chunk_num(struct sprd_dsi *dsi, u16 num)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_chunk_num)
+ ops->dpi_chunk_num(ctx, num);
+}
+/*
+ * Write the null packet size - will only be taken into account when null
+ * packets are enabled.
+ * @dsi pointer to structure holding the DSI Host core information
+ * @size of null packet
+ */
+static inline
+void dsi_hal_dpi_null_packet_size(struct sprd_dsi *dsi, u16 size)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_null_packet_size)
+ ops->dpi_null_packet_size(ctx, size);
+}
+/*
+ * Write video packet size. obligatory for sending video
+ * @dsi pointer to structure holding the DSI Host core information
+ * @size of video packet - containing information
+ */
+static inline
+void dsi_hal_dpi_video_packet_size(struct sprd_dsi *dsi, u16 size)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->dpi_video_packet_size)
+ ops->dpi_video_packet_size(ctx, size);
+}
+/*
+ * Specifiy the size of the packet memory write start/continue
+ * @dsi pointer to structure holding the DSI Host core information
+ * @size size of the packet
+ * note when different than zero (0) eDPI is enabled
+ */
+static inline void dsi_hal_edpi_max_pkt_size(struct sprd_dsi *dsi, u16 size)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->edpi_max_pkt_size)
+ ops->edpi_max_pkt_size(ctx, size);
+}
+
+static inline
+void dsi_hal_edpi_video_hs_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->edpi_video_hs_en)
+ ops->edpi_video_hs_en(ctx, enable);
+}
+
+/*
+ * Enable tear effect acknowledge
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ */
+static inline
+void dsi_hal_tear_effect_ack_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->tear_effect_ack_en)
+ ops->tear_effect_ack_en(ctx, enable);
+}
+/*
+ * Enable packets acknowledge request after each packet transmission
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable (1) - disable (0)
+ */
+static inline
+void dsi_hal_cmd_ack_request_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->cmd_ack_request_en)
+ ops->cmd_ack_request_en(ctx, enable);
+}
+/*
+ * Set DCS command packet transmission to transmission type
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable lp transmit in low power
+ */
+static inline
+void dsi_hal_cmd_mode_lp_cmd_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->cmd_mode_lp_cmd_en)
+ ops->cmd_mode_lp_cmd_en(ctx, enable);
+}
+/*
+ * Set DCS read command packet transmission to transmission type
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable lp transmit in low power
+ */
+static inline
+void dsi_hal_video_mode_lp_cmd_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->video_mode_lp_cmd_en)
+ ops->video_mode_lp_cmd_en(ctx, enable);
+}
+
+/*
+ * Write command header in the generic interface
+ * (which also sends DCS commands) as a subset
+ * @dsi pointer to structure holding the DSI Host core information
+ * @vc of destination
+ * @type type of DCS command
+ * @wc_lsb (if DCS, it is the DCS command)
+ * @wc_msb (only parameter of short DCS packet)
+ */
+static inline void dsi_hal_set_packet_header(struct sprd_dsi *dsi,
+ u8 vc,
+ u8 type,
+ u8 wc_lsb,
+ u8 wc_msb)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->set_packet_header)
+ ops->set_packet_header(ctx, vc, type, wc_lsb, wc_msb);
+}
+/*
+ * Write the payload of the long packet commands
+ * @dsi pointer to structure holding the DSI Host core information
+ * @payload array of bytes of payload
+ */
+static inline
+void dsi_hal_set_packet_payload(struct sprd_dsi *dsi, u32 payload)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->set_packet_payload)
+ ops->set_packet_payload(ctx, payload);
+}
+/*
+ * Write the payload of the long packet commands
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 32-bit data to hold read information
+ */
+static inline u32 dsi_hal_get_rx_payload(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->get_rx_payload)
+ return ops->get_rx_payload(ctx);
+
+ return 0;
+}
+
+/*
+ * Enable Bus Turn-around request
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable
+ */
+static inline void dsi_hal_bta_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->bta_en)
+ ops->bta_en(ctx, enable);
+}
+/*
+ * Enable EOTp reception
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable
+ */
+static inline void dsi_hal_eotp_rx_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->eotp_rx_en)
+ ops->eotp_rx_en(ctx, enable);
+}
+/*
+ * Enable EOTp transmission
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable
+ */
+static inline void dsi_hal_eotp_tx_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->eotp_tx_en)
+ ops->eotp_tx_en(ctx, enable);
+}
+/*
+ * Enable ECC reception, error correction and reporting
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable
+ */
+static inline void dsi_hal_ecc_rx_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->ecc_rx_en)
+ ops->ecc_rx_en(ctx, enable);
+}
+/*
+ * Enable CRC reception, error reporting
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable
+ */
+static inline void dsi_hal_crc_rx_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->crc_rx_en)
+ ops->crc_rx_en(ctx, enable);
+}
+/*
+ * Get status of read command
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if bta was returned
+ */
+static inline bool dsi_hal_is_bta_returned(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_bta_returned)
+ return ops->is_bta_returned(ctx);
+
+ return true;
+}
+/*
+ * Get the FULL status of generic read payload fifo
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if fifo full
+ */
+static inline bool dsi_hal_is_rx_payload_fifo_full(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_rx_payload_fifo_full)
+ return ops->is_rx_payload_fifo_full(ctx);
+
+ return false;
+}
+/*
+ * Get the EMPTY status of generic read payload fifo
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if fifo empty
+ */
+static inline bool dsi_hal_is_rx_payload_fifo_empty(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_rx_payload_fifo_empty)
+ return ops->is_rx_payload_fifo_empty(ctx);
+
+ return true;
+}
+/*
+ * Get the FULL status of generic write payload fifo
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if fifo full
+ */
+static inline bool dsi_hal_is_tx_payload_fifo_full(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_tx_payload_fifo_full)
+ return ops->is_tx_payload_fifo_full(ctx);
+
+ return false;
+}
+/*
+ * Get the EMPTY status of generic write payload fifo
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if fifo empty
+ */
+static inline bool dsi_hal_is_tx_payload_fifo_empty(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_tx_payload_fifo_empty)
+ return ops->is_tx_payload_fifo_empty(ctx);
+
+ return true;
+}
+/*
+ * Get the FULL status of generic command fifo
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if fifo full
+ */
+static inline bool dsi_hal_is_tx_cmd_fifo_full(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_tx_cmd_fifo_full)
+ return ops->is_tx_cmd_fifo_full(ctx);
+
+ return false;
+}
+/*
+ * Get the EMPTY status of generic command fifo
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return 1 if fifo empty
+ */
+static inline bool dsi_hal_is_tx_cmd_fifo_empty(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->is_tx_cmd_fifo_empty)
+ return ops->is_tx_cmd_fifo_empty(ctx);
+
+ return true;
+}
+
+static inline
+bool dsi_hal_wait_tx_payload_fifo_empty(struct sprd_dsi *dsi)
+{
+ int timeout;
+
+ for (timeout = 0; timeout < 5000; timeout++) {
+ if (dsi_hal_is_tx_payload_fifo_empty(dsi))
+ return true;
+ udelay(1);
+ }
+
+ pr_err("tx payload fifo is not empty\n");
+ return false;
+}
+
+static inline bool dsi_hal_wait_tx_cmd_fifo_empty(struct sprd_dsi *dsi)
+{
+ int timeout;
+
+ for (timeout = 0; timeout < 5000; timeout++) {
+ if (dsi_hal_is_tx_cmd_fifo_empty(dsi))
+ return true;
+ udelay(1);
+ }
+
+ pr_err("tx cmd fifo is not empty\n");
+ return false;
+}
+
+static inline bool dsi_hal_wait_rd_resp_completed(struct sprd_dsi *dsi)
+{
+ int timeout;
+
+ for (timeout = 0; timeout < 10000; timeout++) {
+ udelay(10);
+ if (dsi_hal_is_bta_returned(dsi))
+ return true;
+ }
+
+ pr_err("wait read response time out\n");
+ return false;
+}
+
+/* only if DPI */
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch data lane from high speed to low power
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle
+ */
+static inline
+void dsi_hal_datalane_hs2lp_config(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->datalane_hs2lp_config)
+ ops->datalane_hs2lp_config(ctx, byte_cycle);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch the data lane from to low power high speed
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle
+ */
+static inline
+void dsi_hal_datalane_lp2hs_config(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->datalane_lp2hs_config)
+ ops->datalane_lp2hs_config(ctx, byte_cycle);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch clock lane from high speed to low power
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle
+ */
+static inline
+void dsi_hal_clklane_hs2lp_config(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->clklane_hs2lp_config)
+ ops->clklane_hs2lp_config(ctx, byte_cycle);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to switch clock lane from to low power high speed
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle
+ */
+static inline
+void dsi_hal_clklane_lp2hs_config(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->clklane_lp2hs_config)
+ ops->clklane_lp2hs_config(ctx, byte_cycle);
+}
+/*
+ * Configure how many cycles of byte clock would the PHY module take
+ * to turn the bus around to start receiving
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle
+ */
+static inline
+void dsi_hal_max_read_time(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->max_read_time)
+ ops->max_read_time(ctx, byte_cycle);
+}
+/*
+ * Enable the automatic mechanism to stop providing clock in the clock
+ * lane when time allows
+ * @dsi pointer to structure holding the DSI Host core information
+ * @enable
+ */
+static inline void dsi_hal_nc_clk_en(struct sprd_dsi *dsi, int enable)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->nc_clk_en)
+ ops->nc_clk_en(ctx, enable);
+}
+/*
+ * Write transmission escape timeout
+ * a safe guard so that the state machine would reset if transmission
+ * takes too long
+ * @dsi pointer to structure holding the DSI Host core information
+ * @div escape clk division
+ */
+static inline void dsi_hal_tx_escape_division(struct sprd_dsi *dsi, u8 div)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->tx_escape_division)
+ ops->tx_escape_division(ctx, div);
+}
+/* PRESP Time outs */
+/*
+ * configure timeout divisions (so they would have more clock ticks)
+ * @dsi pointer to structure holding the DSI Host core information
+ * @div no of hs cycles before transiting back to LP in
+ * (lane_clk / byte_clk_division_factor)
+ */
+static inline
+void dsi_hal_timeout_clock_division(struct sprd_dsi *dsi, u8 div)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->timeout_clock_division)
+ ops->timeout_clock_division(ctx, div);
+}
+/*
+ * Configure the Low power receive time out
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle count of byte cycles
+ */
+static inline
+void dsi_hal_lp_rx_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->lp_rx_timeout)
+ ops->lp_rx_timeout(ctx, byte_cycle);
+}
+/*
+ * Configure a high speed transmission time out
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle
+ */
+static inline
+void dsi_hal_hs_tx_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->hs_tx_timeout)
+ ops->hs_tx_timeout(ctx, byte_cycle);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after bus turn around
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle period for which the DWC_mipi_dsi_hal_host keeps
+ * the link still, after sending a BTA operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static inline
+void dsi_hal_bta_presp_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->bta_presp_timeout)
+ ops->bta_presp_timeout(ctx, byte_cycle);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after LP data
+ * transmission write requests
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle period for which the DWC_mipi_dsi_hal_host keeps
+ * the link still, after sending a low power write operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static inline
+void dsi_hal_lp_write_presp_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->lp_write_presp_timeout)
+ ops->lp_write_presp_timeout(ctx, byte_cycle);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after LP data
+ * transmission read requests
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle period for which the DWC_mipi_dsi_hal_host keeps
+ * the link still, after sending a low power read operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static inline
+void dsi_hal_lp_read_presp_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->lp_read_presp_timeout)
+ ops->lp_read_presp_timeout(ctx, byte_cycle);
+}
+/*
+ * Timeout for peripheral (for controller to stay still) after HS data
+ * transmission write requests
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle period for which the DWC_mipi_dsi_hal_host keeps
+ * the link still, after sending a high-speed write operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static inline
+void dsi_hal_hs_write_presp_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->hs_write_presp_timeout)
+ ops->hs_write_presp_timeout(ctx, byte_cycle);
+}
+/*
+ * Timeout for peripheral between HS data transmission read requests
+ * @dsi pointer to structure holding the DSI Host core information
+ * @byte_cycle period for which the DWC_mipi_dsi_hal_host keeps
+ * the link still, after sending a high-speed read operation. This period is
+ * measured in cycles of lanebyteclk
+ */
+static inline
+void dsi_hal_hs_read_presp_timeout(struct sprd_dsi *dsi, u16 byte_cycle)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->hs_read_presp_timeout)
+ ops->hs_read_presp_timeout(ctx, byte_cycle);
+}
+/*
+ * Get the error 0 interrupt register status
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return error status 0 value
+ */
+static inline u32 dsi_hal_int0_status(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->int0_status)
+ return ops->int0_status(ctx);
+
+ return 0;
+}
+/*
+ * Get the error 1 interrupt register status
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return error status 1 value
+ */
+static inline u32 dsi_hal_int1_status(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->int1_status)
+ return ops->int1_status(ctx);
+
+ return 0;
+}
+/*
+ * Get the error 2 interrupt register status
+ * @dsi pointer to structure holding the DSI Host core information
+ * @return error status 2 value
+ */
+static inline u32 dsi_hal_int2_status(struct sprd_dsi *dsi)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->int2_status)
+ return ops->int2_status(ctx);
+
+ return 0;
+}
+/*
+ * Configure MASK (hiding) of interrupts coming from error 0 source
+ * @dsi pointer to structure holding the DSI Host core information
+ * @mask to be written to the register
+ */
+static inline void dsi_hal_int0_mask(struct sprd_dsi *dsi, u32 mask)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->int0_mask)
+ ops->int0_mask(ctx, mask);
+}
+/*
+ * Configure MASK (hiding) of interrupts coming from error 0 source
+ * @dsi pointer to structure holding the DSI Host core information
+ * @mask the mask to be written to the register
+ */
+static inline void dsi_hal_int1_mask(struct sprd_dsi *dsi, u32 mask)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->int1_mask)
+ ops->int1_mask(ctx, mask);
+}
+/*
+ * Configure MASK (hiding) of interrupts coming from error 0 source
+ * @dsi pointer to structure holding the DSI Host core information
+ * @mask the mask to be written to the register
+ */
+static inline void dsi_hal_int2_mask(struct sprd_dsi *dsi, u32 mask)
+{
+ struct dsi_core_ops *ops = dsi->core;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ if (ops->int2_mask)
+ ops->int2_mask(ctx, mask);
+}
+
+#endif /* MIPI_DSI_HAL_H_ */
diff --git a/drivers/gpu/drm/sprd/sprd_dphy.c b/drivers/gpu/drm/sprd/sprd_dphy.c
new file mode 100644
index 0000000..f30db90
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dphy.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "sprd_dphy.h"
+
+LIST_HEAD(dphy_pll_head);
+LIST_HEAD(dphy_ppi_head);
+LIST_HEAD(dphy_glb_head);
+
+static int regmap_tst_io_write(void *context, u32 reg, u32 val)
+{
+ struct sprd_dphy *dphy = context;
+
+ if (val > 0xff || reg > 0xff)
+ return -EINVAL;
+
+ pr_debug("reg = 0x%02x, val = 0x%02x\n", reg, val);
+
+ sprd_dphy_test_write(dphy, reg, val);
+
+ return 0;
+}
+
+static int regmap_tst_io_read(void *context, u32 reg, u32 *val)
+{
+ struct sprd_dphy *dphy = context;
+ int ret;
+
+ if (reg > 0xff)
+ return -EINVAL;
+
+ ret = sprd_dphy_test_read(dphy, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+
+ pr_debug("reg = 0x%02x, val = 0x%02x\n", reg, *val);
+ return 0;
+}
+
+static struct regmap_bus regmap_tst_io = {
+ .reg_write = regmap_tst_io_write,
+ .reg_read = regmap_tst_io_read,
+};
+
+static const struct regmap_config byte_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static const struct regmap_config word_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int sprd_dphy_regmap_init(struct sprd_dphy *dphy)
+{
+ struct dphy_context *ctx = &dphy->ctx;
+ struct regmap *regmap;
+
+ if (ctx->apbbase)
+ regmap = devm_regmap_init_mmio(&dphy->dev,
+ (void __iomem *)ctx->apbbase, &word_config);
+ else
+ regmap = devm_regmap_init(&dphy->dev, &regmap_tst_io,
+ dphy, &byte_config);
+
+ if (IS_ERR(regmap)) {
+ DRM_ERROR("dphy regmap init failed\n");
+ return PTR_ERR(regmap);
+ }
+
+ ctx->regmap = regmap;
+
+ return 0;
+}
+
+void sprd_dphy_ulps_enter(struct sprd_dphy *dphy)
+{
+ DRM_INFO("dphy ulps enter\n");
+ sprd_dphy_hs_clk_en(dphy, false);
+ sprd_dphy_data_ulps_enter(dphy);
+ sprd_dphy_clk_ulps_enter(dphy);
+}
+
+void sprd_dphy_ulps_exit(struct sprd_dphy *dphy)
+{
+ DRM_INFO("dphy ulps exit\n");
+ sprd_dphy_force_pll(dphy, true);
+ sprd_dphy_clk_ulps_exit(dphy);
+ sprd_dphy_data_ulps_exit(dphy);
+ sprd_dphy_force_pll(dphy, false);
+}
+
+int sprd_dphy_resume(struct sprd_dphy *dphy)
+{
+ int ret;
+
+ if (dphy->glb && dphy->glb->power)
+ dphy->glb->power(&dphy->ctx, true);
+ if (dphy->glb && dphy->glb->enable)
+ dphy->glb->enable(&dphy->ctx);
+
+ ret = sprd_dphy_configure(dphy);
+ if (ret) {
+ DRM_ERROR("sprd dphy init failed\n");
+ return -EINVAL;
+ }
+
+ DRM_INFO("dphy resume OK\n");
+ return ret;
+}
+
+int sprd_dphy_suspend(struct sprd_dphy *dphy)
+{
+ int ret;
+
+ ret = sprd_dphy_close(dphy);
+ if (ret)
+ DRM_ERROR("sprd dphy close failed\n");
+
+ if (dphy->glb && dphy->glb->disable)
+ dphy->glb->disable(&dphy->ctx);
+ if (dphy->glb && dphy->glb->power)
+ dphy->glb->power(&dphy->ctx, false);
+
+ DRM_INFO("dphy suspend OK\n");
+ return ret;
+}
+
+static int sprd_dphy_context_init(struct sprd_dphy *dphy,
+ struct device_node *np)
+{
+ struct resource r;
+ u32 tmp;
+
+ dphy->ctx.chip_id = 0xffffffff;
+
+ if (dphy->glb && dphy->glb->parse_dt)
+ dphy->glb->parse_dt(&dphy->ctx, np);
+
+ if (!of_address_to_resource(np, 0, &r)) {
+ dphy->ctx.ctrlbase = (unsigned long)
+ ioremap_nocache(r.start, resource_size(&r));
+ if (dphy->ctx.ctrlbase == 0) {
+ DRM_ERROR("dphy ctrlbase ioremap failed\n");
+ return -EFAULT;
+ }
+ } else {
+ DRM_ERROR("parse dphy ctrl reg base failed\n");
+ return -EINVAL;
+ }
+
+ if (!of_address_to_resource(np, 1, &r)) {
+ DRM_INFO("this dphy has apb reg base\n");
+ dphy->ctx.apbbase = (unsigned long)
+ ioremap_nocache(r.start, resource_size(&r));
+ if (dphy->ctx.apbbase == 0) {
+ DRM_ERROR("dphy apbbase ioremap failed\n");
+ return -EFAULT;
+ }
+ }
+
+ if (!of_property_read_u32(np, "dev-id", &tmp))
+ dphy->ctx.id = tmp;
+
+ if (!of_property_read_u32(np, "sprd,mipi-drive-capability", &tmp))
+ dphy->ctx.capability = tmp;
+
+ mutex_init(&dphy->ctx.hop_lock);
+
+ return 0;
+}
+
+static int sprd_dphy_probe(struct platform_device *pdev)
+{
+ struct sprd_dphy *dphy;
+ struct device *dsi_dev;
+ int ret;
+
+ dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
+ if (!dphy)
+ return -ENOMEM;
+
+ dsi_dev = sprd_disp_pipe_get_input(&pdev->dev);
+ if (!dsi_dev)
+ return -ENODEV;
+
+ ret = sprd_dphy_context_init(dphy, pdev->dev.of_node);
+ if (ret)
+ return ret;
+
+ sprd_dphy_regmap_init(dphy);
+ platform_set_drvdata(pdev, dphy);
+
+ DRM_INFO("dphy driver probe success\n");
+
+ return 0;
+}
+
+static const struct of_device_id dt_ids[] = {
+ { .compatible = "sprd,dsi-phy", },
+ {},
+};
+
+static struct platform_driver sprd_dphy_driver = {
+ .probe = sprd_dphy_probe,
+ .driver = {
+ .name = "sprd-dphy-drv",
+ .of_match_table = of_match_ptr(dt_ids),
+ }
+};
+
+module_platform_driver(sprd_dphy_driver);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("Unisoc SoC MIPI DSI PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_dphy.h b/drivers/gpu/drm/sprd/sprd_dphy.h
new file mode 100644
index 0000000..c35ffa4
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dphy.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _SPRD_DPHY_H_
+#define _SPRD_DPHY_H_
+
+#include <asm/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_print.h>
+
+#include "disp_lib.h"
+
+struct dphy_context {
+ struct regmap *regmap;
+ unsigned long ctrlbase;
+ unsigned long apbbase;
+ struct mutex hop_lock;
+ u32 freq;
+ u8 lanes;
+ u8 id;
+ u8 capability;
+ u32 chip_id;
+};
+
+struct dphy_pll_ops {
+ int (*pll_config)(struct dphy_context *ctx);
+ int (*timing_config)(struct dphy_context *ctx);
+ int (*hop_config)(struct dphy_context *ctx, int delta, int period);
+ int (*ssc_en)(struct dphy_context *ctx, bool en);
+ void (*force_pll)(struct dphy_context *ctx, int force);
+};
+
+struct dphy_ppi_ops {
+ void (*rstz)(struct dphy_context *ctx, int level);
+ void (*shutdownz)(struct dphy_context *ctx, int level);
+ void (*force_pll)(struct dphy_context *ctx, int force);
+ void (*clklane_ulps_rqst)(struct dphy_context *ctx, int en);
+ void (*clklane_ulps_exit)(struct dphy_context *ctx, int en);
+ void (*datalane_ulps_rqst)(struct dphy_context *ctx, int en);
+ void (*datalane_ulps_exit)(struct dphy_context *ctx, int en);
+ void (*stop_wait_time)(struct dphy_context *ctx, u8 byte_clk);
+ void (*datalane_en)(struct dphy_context *ctx);
+ void (*clklane_en)(struct dphy_context *ctx, int en);
+ void (*clk_hs_rqst)(struct dphy_context *ctx, int en);
+ u8 (*is_rx_direction)(struct dphy_context *ctx);
+ u8 (*is_pll_locked)(struct dphy_context *ctx);
+ u8 (*is_rx_ulps_esc_lane0)(struct dphy_context *ctx);
+ u8 (*is_stop_state_clklane)(struct dphy_context *ctx);
+ u8 (*is_stop_state_datalane)(struct dphy_context *ctx);
+ u8 (*is_ulps_active_datalane)(struct dphy_context *ctx);
+ u8 (*is_ulps_active_clklane)(struct dphy_context *ctx);
+ void (*tst_clk)(struct dphy_context *ctx, u8 level);
+ void (*tst_clr)(struct dphy_context *ctx, u8 level);
+ void (*tst_en)(struct dphy_context *ctx, u8 level);
+ u8 (*tst_dout)(struct dphy_context *ctx);
+ void (*tst_din)(struct dphy_context *ctx, u8 data);
+ void (*bist_en)(struct dphy_context *ctx, int en);
+ u8 (*is_bist_ok)(struct dphy_context *ctx);
+};
+
+struct dphy_glb_ops {
+ int (*parse_dt)(struct dphy_context *ctx,
+ struct device_node *np);
+ void (*enable)(struct dphy_context *ctx);
+ void (*disable)(struct dphy_context *ctx);
+ void (*power)(struct dphy_context *ctx, int enable);
+};
+
+struct sprd_dphy {
+ struct device dev;
+ struct dphy_context ctx;
+ struct dphy_ppi_ops *ppi;
+ struct dphy_pll_ops *pll;
+ struct dphy_glb_ops *glb;
+};
+
+extern struct list_head dphy_pll_head;
+extern struct list_head dphy_ppi_head;
+extern struct list_head dphy_glb_head;
+
+#define dphy_pll_ops_register(entry) \
+ disp_ops_register(entry, &dphy_pll_head)
+#define dphy_ppi_ops_register(entry) \
+ disp_ops_register(entry, &dphy_ppi_head)
+#define dphy_glb_ops_register(entry) \
+ disp_ops_register(entry, &dphy_glb_head)
+
+#define dphy_pll_ops_attach(str) \
+ disp_ops_attach(str, &dphy_pll_head)
+#define dphy_ppi_ops_attach(str) \
+ disp_ops_attach(str, &dphy_ppi_head)
+#define dphy_glb_ops_attach(str) \
+ disp_ops_attach(str, &dphy_glb_head)
+
+void sprd_dphy_ulps_enter(struct sprd_dphy *dphy);
+void sprd_dphy_ulps_exit(struct sprd_dphy *dphy);
+int sprd_dphy_resume(struct sprd_dphy *dphy);
+int sprd_dphy_suspend(struct sprd_dphy *dphy);
+
+int sprd_dphy_configure(struct sprd_dphy *dphy);
+void sprd_dphy_reset(struct sprd_dphy *dphy);
+void sprd_dphy_shutdown(struct sprd_dphy *dphy);
+int sprd_dphy_hop_config(struct sprd_dphy *dphy, int delta, int period);
+int sprd_dphy_ssc_en(struct sprd_dphy *dphy, bool en);
+int sprd_dphy_close(struct sprd_dphy *dphy);
+int sprd_dphy_data_ulps_enter(struct sprd_dphy *dphy);
+int sprd_dphy_data_ulps_exit(struct sprd_dphy *dphy);
+int sprd_dphy_clk_ulps_enter(struct sprd_dphy *dphy);
+int sprd_dphy_clk_ulps_exit(struct sprd_dphy *dphy);
+void sprd_dphy_force_pll(struct sprd_dphy *dphy, bool enable);
+void sprd_dphy_hs_clk_en(struct sprd_dphy *dphy, bool enable);
+void sprd_dphy_test_write(struct sprd_dphy *dphy, u8 address, u8 data);
+u8 sprd_dphy_test_read(struct sprd_dphy *dphy, u8 address);
+
+#endif /* _SPRD_DPHY_H_ */
diff --git a/drivers/gpu/drm/sprd/sprd_dsi.c b/drivers/gpu/drm/sprd/sprd_dsi.c
new file mode 100644
index 0000000..9dc711f
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dsi.c
@@ -0,0 +1,722 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <video/mipi_display.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+
+#include "disp_lib.h"
+#include "sprd_dpu.h"
+#include "sprd_dsi.h"
+#include "dsi/sprd_dsi_api.h"
+
+#define encoder_to_dsi(encoder) \
+ container_of(encoder, struct sprd_dsi, encoder)
+#define host_to_dsi(host) \
+ container_of(host, struct sprd_dsi, host)
+#define connector_to_dsi(connector) \
+ container_of(connector, struct sprd_dsi, connector)
+
+LIST_HEAD(dsi_core_head);
+LIST_HEAD(dsi_glb_head);
+static DEFINE_MUTEX(dsi_lock);
+
+static int sprd_dsi_resume(struct sprd_dsi *dsi)
+{
+ if (dsi->glb && dsi->glb->power)
+ dsi->glb->power(&dsi->ctx, true);
+ if (dsi->glb && dsi->glb->enable)
+ dsi->glb->enable(&dsi->ctx);
+ if (dsi->glb && dsi->glb->reset)
+ dsi->glb->reset(&dsi->ctx);
+
+ sprd_dsi_init(dsi);
+
+ if (dsi->ctx.work_mode == DSI_MODE_VIDEO)
+ sprd_dsi_dpi_video(dsi);
+ else
+ sprd_dsi_edpi_video(dsi);
+
+ DRM_INFO("dsi resume OK\n");
+ return 0;
+}
+
+static int sprd_dsi_suspend(struct sprd_dsi *dsi)
+{
+ sprd_dsi_uninit(dsi);
+
+ if (dsi->glb && dsi->glb->disable)
+ dsi->glb->disable(&dsi->ctx);
+ if (dsi->glb && dsi->glb->power)
+ dsi->glb->power(&dsi->ctx, false);
+
+ DRM_INFO("dsi suspend OK\n");
+ return 0;
+}
+
+/* FIXME: This should be removed in the feature. */
+static void sprd_sharkl3_workaround(struct sprd_dsi *dsi)
+{
+ /* the sharkl3 AA Chip needs to reset D-PHY before HS transmition */
+ if (dsi->phy->ctx.chip_id == 0) {
+ sprd_dphy_reset(dsi->phy);
+ mdelay(1);
+ }
+}
+
+static void sprd_dsi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct sprd_dsi *dsi = encoder_to_dsi(encoder);
+ struct sprd_dpu *dpu = crtc_to_dpu(encoder->crtc);
+ static bool is_enabled = true;
+
+ DRM_INFO("%s()\n", __func__);
+
+ /* add if condition to avoid resume dsi for SR feature */
+ if (encoder->crtc->state->mode_changed &&
+ !encoder->crtc->state->active_changed)
+ return;
+
+ mutex_lock(&dsi_lock);
+
+ if (dsi->ctx.is_inited) {
+ mutex_unlock(&dsi_lock);
+ DRM_ERROR("dsi is inited\n");
+ return;
+ }
+
+ if (is_enabled) {
+ is_enabled = false;
+ dsi->ctx.is_inited = true;
+ mutex_unlock(&dsi_lock);
+ return;
+ }
+
+ pm_runtime_get_sync(dsi->dev.parent);
+
+ sprd_dsi_resume(dsi);
+ sprd_dphy_resume(dsi->phy);
+
+ sprd_dsi_lp_cmd_enable(dsi, true);
+
+ if (dsi->panel) {
+ drm_panel_prepare(dsi->panel);
+ drm_panel_enable(dsi->panel);
+ }
+
+ sprd_dsi_set_work_mode(dsi, dsi->ctx.work_mode);
+ sprd_dsi_state_reset(dsi);
+
+ sprd_sharkl3_workaround(dsi);
+
+ if (dsi->ctx.nc_clk_en)
+ sprd_dsi_nc_clk_en(dsi, true);
+ else
+ sprd_dphy_hs_clk_en(dsi->phy, true);
+
+ sprd_dpu_run(dpu);
+
+ dsi->ctx.is_inited = true;
+ mutex_unlock(&dsi_lock);
+}
+
+static void sprd_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct sprd_dsi *dsi = encoder_to_dsi(encoder);
+ struct sprd_dpu *dpu = crtc_to_dpu(encoder->crtc);
+
+ DRM_INFO("%s()\n", __func__);
+
+ /* add if condition to avoid suspend dsi for SR feature */
+ if (encoder->crtc->state->mode_changed &&
+ !encoder->crtc->state->active_changed)
+ return;
+
+ mutex_lock(&dsi_lock);
+
+ if (!dsi->ctx.is_inited) {
+ mutex_unlock(&dsi_lock);
+ DRM_ERROR("dsi isn't inited\n");
+ return;
+ }
+
+ sprd_dpu_stop(dpu);
+ sprd_dsi_set_work_mode(dsi, DSI_MODE_CMD);
+ sprd_dsi_lp_cmd_enable(dsi, true);
+
+ if (dsi->panel) {
+ drm_panel_disable(dsi->panel);
+ sprd_dphy_ulps_enter(dsi->phy);
+ drm_panel_unprepare(dsi->panel);
+ }
+
+ sprd_dphy_suspend(dsi->phy);
+ sprd_dsi_suspend(dsi);
+
+ pm_runtime_put(dsi->dev.parent);
+
+ dsi->ctx.is_inited = false;
+ mutex_unlock(&dsi_lock);
+}
+
+static void sprd_dsi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct sprd_dsi *dsi = encoder_to_dsi(encoder);
+
+ DRM_INFO("%s() set mode: %s\n", __func__, dsi->mode->name);
+}
+
+static int sprd_dsi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ DRM_INFO("%s()\n", __func__);
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs sprd_encoder_helper_funcs = {
+ .atomic_check = sprd_dsi_encoder_atomic_check,
+ .mode_set = sprd_dsi_encoder_mode_set,
+ .enable = sprd_dsi_encoder_enable,
+ .disable = sprd_dsi_encoder_disable
+};
+
+static const struct drm_encoder_funcs sprd_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static int sprd_dsi_encoder_init(struct drm_device *drm,
+ struct sprd_dsi *dsi)
+{
+ struct drm_encoder *encoder = &dsi->encoder;
+ struct device *dev = dsi->host.dev;
+ u32 crtc_mask;
+ int ret;
+
+ crtc_mask = drm_of_find_possible_crtcs(drm, dev->of_node);
+ if (!crtc_mask) {
+ DRM_ERROR("failed to find crtc mask\n");
+ return -EINVAL;
+ }
+ DRM_INFO("find possible crtcs: 0x%08x\n", crtc_mask);
+
+ encoder->possible_crtcs = crtc_mask;
+ ret = drm_encoder_init(drm, encoder, &sprd_encoder_funcs,
+ DRM_MODE_ENCODER_DSI, NULL);
+ if (ret) {
+ DRM_ERROR("failed to init dsi encoder\n");
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &sprd_encoder_helper_funcs);
+
+ return 0;
+}
+
+static int sprd_dsi_find_panel(struct sprd_dsi *dsi)
+{
+ struct device *dev = dsi->host.dev;
+ struct device_node *child, *lcds_node;
+ struct drm_panel *panel;
+
+ /* search /lcds child node first */
+ lcds_node = of_find_node_by_path("/lcds");
+ for_each_child_of_node(lcds_node, child) {
+ panel = of_drm_find_panel(child);
+ if (panel) {
+ dsi->panel = panel;
+ return 0;
+ }
+ }
+
+ /*
+ * If /lcds child node search failed, we search
+ * the child of dsi host node.
+ */
+ for_each_child_of_node(dev->of_node, child) {
+ panel = of_drm_find_panel(child);
+ if (panel) {
+ dsi->panel = panel;
+ return 0;
+ }
+ }
+
+ DRM_ERROR("of_drm_find_panel() failed\n");
+ return -ENODEV;
+}
+
+static int sprd_dsi_phy_attach(struct sprd_dsi *dsi)
+{
+ struct device *dev;
+
+ dev = sprd_disp_pipe_get_output(&dsi->dev);
+ if (!dev)
+ return -ENODEV;
+
+ dsi->phy = dev_get_drvdata(dev);
+ if (!dsi->phy) {
+ DRM_ERROR("dsi attach phy failed\n");
+ return -EINVAL;
+ }
+
+ dsi->phy->ctx.lanes = dsi->ctx.lanes;
+
+ return 0;
+}
+
+static int sprd_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *slave)
+{
+ struct sprd_dsi *dsi = host_to_dsi(host);
+ struct dsi_context *ctx = &dsi->ctx;
+ struct device_node *lcd_node;
+ u32 val;
+ int ret;
+
+ DRM_INFO("%s()\n", __func__);
+
+ dsi->slave = slave;
+ ctx->lanes = slave->lanes;
+ ctx->format = slave->format;
+
+ if (slave->mode_flags & MIPI_DSI_MODE_VIDEO)
+ ctx->work_mode = DSI_MODE_VIDEO;
+ else
+ ctx->work_mode = DSI_MODE_CMD;
+
+ if (slave->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ ctx->burst_mode = VIDEO_BURST_WITH_SYNC_PULSES;
+ else if (slave->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ ctx->burst_mode = VIDEO_NON_BURST_WITH_SYNC_PULSES;
+ else
+ ctx->burst_mode = VIDEO_NON_BURST_WITH_SYNC_EVENTS;
+
+ if (slave->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
+ ctx->nc_clk_en = true;
+
+ ret = sprd_dsi_phy_attach(dsi);
+ if (ret)
+ return ret;
+
+ ret = sprd_dsi_find_panel(dsi);
+ if (ret)
+ return ret;
+
+ lcd_node = dsi->panel->dev->of_node;
+
+ ret = of_property_read_u32(lcd_node, "sprd,phy-bit-clock", &val);
+ if (!ret) {
+ dsi->phy->ctx.freq = val;
+ ctx->byte_clk = val / 8;
+ } else {
+ dsi->phy->ctx.freq = 500000;
+ ctx->byte_clk = 500000 / 8;
+ }
+
+ ret = of_property_read_u32(lcd_node, "sprd,phy-escape-clock", &val);
+ if (!ret)
+ ctx->esc_clk = val > 20000 ? 20000 : val;
+ else
+ ctx->esc_clk = 20000;
+
+ return 0;
+}
+
+static int sprd_dsi_host_detach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *slave)
+{
+ DRM_INFO("%s()\n", __func__);
+ /* do nothing */
+ return 0;
+}
+
+static ssize_t sprd_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct sprd_dsi *dsi = host_to_dsi(host);
+ const u8 *tx_buf = msg->tx_buf;
+
+ if (msg->rx_buf && msg->rx_len) {
+ u8 lsb = (msg->tx_len > 0) ? tx_buf[0] : 0;
+ u8 msb = (msg->tx_len > 1) ? tx_buf[1] : 0;
+
+ return sprd_dsi_rd_pkt(dsi, msg->channel, msg->type,
+ msb, lsb, msg->rx_buf, msg->rx_len);
+ }
+
+ if (msg->tx_buf && msg->tx_len)
+ return sprd_dsi_wr_pkt(dsi, msg->channel, msg->type,
+ tx_buf, msg->tx_len);
+
+ return 0;
+}
+
+static const struct mipi_dsi_host_ops sprd_dsi_host_ops = {
+ .attach = sprd_dsi_host_attach,
+ .detach = sprd_dsi_host_detach,
+ .transfer = sprd_dsi_host_transfer,
+};
+
+static int sprd_dsi_host_init(struct device *dev, struct sprd_dsi *dsi)
+{
+ int ret;
+
+ dsi->host.dev = dev;
+ dsi->host.ops = &sprd_dsi_host_ops;
+
+ ret = mipi_dsi_host_register(&dsi->host);
+ if (ret)
+ DRM_ERROR("failed to register dsi host\n");
+
+ return ret;
+}
+
+static int sprd_dsi_connector_get_modes(struct drm_connector *connector)
+{
+ struct sprd_dsi *dsi = connector_to_dsi(connector);
+
+ DRM_INFO("%s()\n", __func__);
+
+ return drm_panel_get_modes(dsi->panel);
+}
+
+static enum drm_mode_status
+sprd_dsi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct sprd_dsi *dsi = connector_to_dsi(connector);
+ struct drm_display_mode *pmode;
+
+ DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
+
+ if (mode->type & DRM_MODE_TYPE_PREFERRED) {
+ dsi->mode = mode;
+ drm_display_mode_to_videomode(dsi->mode, &dsi->ctx.vm);
+ }
+
+ if (mode->type & DRM_MODE_TYPE_BUILTIN) {
+ list_for_each_entry(pmode, &connector->modes, head) {
+ if (pmode->type & DRM_MODE_TYPE_PREFERRED) {
+ list_del(&pmode->head);
+ drm_mode_destroy(connector->dev, pmode);
+ dsi->mode = mode;
+ break;
+ }
+ }
+ }
+
+ return MODE_OK;
+}
+
+static struct drm_encoder *
+sprd_dsi_connector_best_encoder(struct drm_connector *connector)
+{
+ struct sprd_dsi *dsi = connector_to_dsi(connector);
+
+ DRM_INFO("%s()\n", __func__);
+ return &dsi->encoder;
+}
+
+static struct drm_connector_helper_funcs sprd_dsi_connector_helper_funcs = {
+ .get_modes = sprd_dsi_connector_get_modes,
+ .mode_valid = sprd_dsi_connector_mode_valid,
+ .best_encoder = sprd_dsi_connector_best_encoder,
+};
+
+static enum drm_connector_status
+sprd_dsi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct sprd_dsi *dsi = connector_to_dsi(connector);
+
+ DRM_INFO("%s()\n", __func__);
+
+ if (dsi->panel) {
+ drm_panel_attach(dsi->panel, connector);
+ return connector_status_connected;
+ }
+
+ return connector_status_disconnected;
+}
+
+static void sprd_dsi_connector_destroy(struct drm_connector *connector)
+{
+ DRM_INFO("%s()\n", __func__);
+
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs sprd_dsi_atomic_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = sprd_dsi_connector_detect,
+ .destroy = sprd_dsi_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int sprd_dsi_connector_init(struct drm_device *drm, struct sprd_dsi *dsi)
+{
+ struct drm_encoder *encoder = &dsi->encoder;
+ struct drm_connector *connector = &dsi->connector;
+ int ret;
+
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init(drm, connector,
+ &sprd_dsi_atomic_connector_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ if (ret) {
+ DRM_ERROR("drm_connector_init() failed\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector,
+ &sprd_dsi_connector_helper_funcs);
+
+ drm_connector_attach_encoder(connector, encoder);
+
+ return 0;
+}
+
+static int sprd_dsi_bridge_attach(struct sprd_dsi *dsi)
+{
+ struct drm_encoder *encoder = &dsi->encoder;
+ struct drm_bridge *bridge = dsi->bridge;
+ struct device *dev = dsi->host.dev;
+ struct device_node *bridge_node;
+ int ret;
+
+ bridge_node = of_graph_get_remote_node(dev->of_node, 2, 0);
+ if (!bridge_node)
+ return 0;
+
+ bridge = of_drm_find_bridge(bridge_node);
+ if (!bridge) {
+ DRM_ERROR("of_drm_find_bridge() failed\n");
+ return -ENODEV;
+ }
+ dsi->bridge = bridge;
+
+ ret = drm_bridge_attach(encoder, bridge, NULL);
+ if (ret) {
+ DRM_ERROR("failed to attach external bridge\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sprd_dsi_glb_init(struct sprd_dsi *dsi)
+{
+ if (dsi->glb && dsi->glb->power)
+ dsi->glb->power(&dsi->ctx, true);
+ if (dsi->glb && dsi->glb->enable)
+ dsi->glb->enable(&dsi->ctx);
+
+ return 0;
+}
+
+static irqreturn_t sprd_dsi_isr(int irq, void *data)
+{
+ u32 status = 0;
+ struct sprd_dsi *dsi = data;
+
+ if (!dsi) {
+ DRM_ERROR("dsi pointer is NULL\n");
+ return IRQ_HANDLED;
+ }
+
+ if (dsi->ctx.irq0 == irq)
+ status = sprd_dsi_int_status(dsi, 0);
+ else if (dsi->ctx.irq1 == irq)
+ status = sprd_dsi_int_status(dsi, 1);
+
+ if (status & DSI_INT_STS_NEED_SOFT_RESET)
+ sprd_dsi_state_reset(dsi);
+
+ return IRQ_HANDLED;
+}
+
+static int sprd_dsi_irq_request(struct sprd_dsi *dsi)
+{
+ int ret;
+ int irq0, irq1;
+ struct dsi_context *ctx = &dsi->ctx;
+
+ irq0 = irq_of_parse_and_map(dsi->host.dev->of_node, 0);
+ if (irq0) {
+ DRM_INFO("dsi irq0 num = %d\n", irq0);
+ ret = request_irq(irq0, sprd_dsi_isr, 0, "DSI_INT0", dsi);
+ if (ret) {
+ DRM_ERROR("dsi failed to request irq int0!\n");
+ return -EINVAL;
+ }
+ }
+ ctx->irq0 = irq0;
+
+ irq1 = irq_of_parse_and_map(dsi->host.dev->of_node, 1);
+ if (irq1) {
+ DRM_INFO("dsi irq1 num = %d\n", irq1);
+ ret = request_irq(irq1, sprd_dsi_isr, 0, "DSI_INT1", dsi);
+ if (ret) {
+ DRM_ERROR("dsi failed to request irq int1!\n");
+ return -EINVAL;
+ }
+ }
+ ctx->irq1 = irq1;
+
+ return 0;
+}
+
+static int sprd_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct sprd_dsi *dsi = dev_get_drvdata(dev);
+ int ret;
+
+ ret = sprd_dsi_encoder_init(drm, dsi);
+ if (ret)
+ goto cleanup_host;
+
+ ret = sprd_dsi_connector_init(drm, dsi);
+ if (ret)
+ goto cleanup_encoder;
+
+ ret = sprd_dsi_bridge_attach(dsi);
+ if (ret)
+ goto cleanup_connector;
+
+ ret = sprd_dsi_glb_init(dsi);
+ if (ret)
+ goto cleanup_connector;
+
+ ret = sprd_dsi_irq_request(dsi);
+ if (ret)
+ goto cleanup_connector;
+
+ return 0;
+
+cleanup_connector:
+ drm_connector_cleanup(&dsi->connector);
+cleanup_encoder:
+ drm_encoder_cleanup(&dsi->encoder);
+cleanup_host:
+ mipi_dsi_host_unregister(&dsi->host);
+ return ret;
+}
+
+static void sprd_dsi_unbind(struct device *dev,
+ struct device *master, void *data)
+{
+ /* do nothing */
+ DRM_INFO("%s()\n", __func__);
+
+}
+
+static const struct component_ops dsi_component_ops = {
+ .bind = sprd_dsi_bind,
+ .unbind = sprd_dsi_unbind,
+};
+
+static int sprd_dsi_context_init(struct sprd_dsi *dsi, struct device_node *np)
+{
+ struct dsi_context *ctx = &dsi->ctx;
+ struct resource r;
+
+ if (dsi->glb && dsi->glb->parse_dt)
+ dsi->glb->parse_dt(&dsi->ctx, np);
+
+ if (of_address_to_resource(np, 0, &r)) {
+ DRM_ERROR("parse dsi ctrl reg base failed\n");
+ return -ENODEV;
+ }
+ ctx->base = (unsigned long)
+ ioremap_nocache(r.start, resource_size(&r));
+ if (ctx->base == 0) {
+ DRM_ERROR("dsi ctrl reg base ioremap failed\n");
+ return -ENODEV;
+ }
+
+ ctx->data_hs2lp = 120;
+ ctx->data_lp2hs = 500;
+ ctx->clk_hs2lp = 4;
+ ctx->clk_lp2hs = 15;
+ ctx->max_rd_time = 6000;
+ ctx->int0_mask = 0xffffffff;
+ ctx->int1_mask = 0xffffffff;
+
+ return 0;
+}
+
+static int sprd_dsi_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct sprd_dsi *dsi;
+ int ret;
+
+ dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi) {
+ DRM_ERROR("failed to allocate dsi data.\n");
+ return -ENOMEM;
+ }
+
+ ret = sprd_dsi_context_init(dsi, np);
+ if (ret)
+ return -EINVAL;
+
+ platform_set_drvdata(pdev, dsi);
+
+ ret = sprd_dsi_host_init(&pdev->dev, dsi);
+ if (ret)
+ return ret;
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ return component_add(&pdev->dev, &dsi_component_ops);
+}
+
+static int sprd_dsi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &dsi_component_ops);
+
+ return 0;
+}
+
+static const struct of_device_id sprd_dsi_of_match[] = {
+ {.compatible = "sprd,dsi-host"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sprd_dsi_of_match);
+
+static struct platform_driver sprd_dsi_driver = {
+ .probe = sprd_dsi_probe,
+ .remove = sprd_dsi_remove,
+ .driver = {
+ .name = "sprd-dsi-drv",
+ .of_match_table = sprd_dsi_of_match,
+ },
+};
+
+module_platform_driver(sprd_dsi_driver);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("Unisoc MIPI DSI HOST Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_dsi.h b/drivers/gpu/drm/sprd/sprd_dsi.h
new file mode 100644
index 0000000..0f8f40c
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dsi.h
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef __SPRD_DSI_H__
+#define __SPRD_DSI_H__
+
+#include <linux/of.h>
+#include <linux/device.h>
+#include <video/videomode.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_print.h>
+#include <drm/drm_panel.h>
+
+#include "disp_lib.h"
+#include "sprd_dphy.h"
+
+#define DSI_INT_STS_NEED_SOFT_RESET BIT(0)
+#define DSI_INT_STS_NEED_HARD_RESET BIT(1)
+
+enum dsi_work_mode {
+ DSI_MODE_CMD = 0,
+ DSI_MODE_VIDEO
+};
+
+enum video_burst_mode {
+ VIDEO_NON_BURST_WITH_SYNC_PULSES = 0,
+ VIDEO_NON_BURST_WITH_SYNC_EVENTS,
+ VIDEO_BURST_WITH_SYNC_PULSES
+};
+
+enum dsi_color_coding {
+ COLOR_CODE_16BIT_CONFIG1 = 0,
+ COLOR_CODE_16BIT_CONFIG2 = 1,
+ COLOR_CODE_16BIT_CONFIG3 = 2,
+ COLOR_CODE_18BIT_CONFIG1 = 3,
+ COLOR_CODE_18BIT_CONFIG2 = 4,
+ COLOR_CODE_24BIT = 5,
+ COLOR_CODE_20BIT_YCC422_LOOSELY = 6,
+ COLOR_CODE_24BIT_YCC422 = 7,
+ COLOR_CODE_16BIT_YCC422 = 8,
+ COLOR_CODE_30BIT = 9,
+ COLOR_CODE_36BIT = 10,
+ COLOR_CODE_12BIT_YCC420 = 11,
+ COLOR_CODE_COMPRESSTION = 12,
+ COLOR_CODE_MAX
+};
+
+struct dsi_context {
+ unsigned long base;
+ u8 id;
+ u8 channel;
+ u8 lanes;
+ u32 format;
+ u8 work_mode;
+ u8 burst_mode;
+ struct videomode vm;
+
+ int irq0;
+ int irq1;
+ u32 int0_mask;
+ u32 int1_mask;
+ bool is_inited;
+
+ /* byte clock [KHz] */
+ u32 byte_clk;
+ /* escape clock [KHz] */
+ u32 esc_clk;
+
+ /* maximum time (ns) for data lanes from HS to LP */
+ u16 data_hs2lp;
+ /* maximum time (ns) for data lanes from LP to HS */
+ u16 data_lp2hs;
+ /* maximum time (ns) for clk lanes from HS to LP */
+ u16 clk_hs2lp;
+ /* maximum time (ns) for clk lanes from LP to HS */
+ u16 clk_lp2hs;
+ /* maximum time (ns) for BTA operation - REQUIRED */
+ u16 max_rd_time;
+
+ /* is 18-bit loosely packets (valid only when BPP == 18) */
+ bool is_18_loosely;
+ /* enable receiving frame ack packets - for video mode */
+ bool frame_ack_en;
+ /* enable receiving tear effect ack packets - for cmd mode */
+ bool te_ack_en;
+ /* enable non coninuous clock for energy saving */
+ bool nc_clk_en;
+};
+
+struct dsi_core_ops {
+ bool (*check_version)(struct dsi_context *ctx);
+ void (*power_en)(struct dsi_context *ctx, int enable);
+ void (*video_mode)(struct dsi_context *ctx);
+ void (*cmd_mode)(struct dsi_context *ctx);
+ bool (*is_cmd_mode)(struct dsi_context *ctx);
+ void (*rx_vcid)(struct dsi_context *ctx, u8 vc);
+ void (*video_vcid)(struct dsi_context *ctx, u8 vc);
+ void (*dpi_video_burst_mode)(struct dsi_context *ctx, int mode);
+ void (*dpi_color_coding)(struct dsi_context *ctx, int coding);
+ void (*dpi_18_loosely_packet_en)(struct dsi_context *ctx, int enable);
+ void (*dpi_color_mode_pol)(struct dsi_context *ctx, int active_low);
+ void (*dpi_shut_down_pol)(struct dsi_context *ctx, int active_low);
+ void (*dpi_hsync_pol)(struct dsi_context *ctx, int active_low);
+ void (*dpi_vsync_pol)(struct dsi_context *ctx, int active_low);
+ void (*dpi_data_en_pol)(struct dsi_context *ctx, int active_low);
+ void (*dpi_sig_delay)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*dpi_hline_time)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*dpi_hsync_time)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*dpi_hbp_time)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*dpi_vact)(struct dsi_context *ctx, u16 lines);
+ void (*dpi_vfp)(struct dsi_context *ctx, u16 lines);
+ void (*dpi_vbp)(struct dsi_context *ctx, u16 lines);
+ void (*dpi_vsync)(struct dsi_context *ctx, u16 lines);
+ void (*dpi_hporch_lp_en)(struct dsi_context *ctx, int enable);
+ void (*dpi_vporch_lp_en)(struct dsi_context *ctx, int enable);
+ void (*dpi_frame_ack_en)(struct dsi_context *ctx, int enable);
+ void (*dpi_null_packet_en)(struct dsi_context *ctx, int enable);
+ void (*dpi_multi_packet_en)(struct dsi_context *ctx, int enable);
+ void (*dpi_chunk_num)(struct dsi_context *ctx, u16 no);
+ void (*dpi_null_packet_size)(struct dsi_context *ctx, u16 size);
+ void (*dpi_video_packet_size)(struct dsi_context *ctx, u16 size);
+ void (*edpi_max_pkt_size)(struct dsi_context *ctx, u16 size);
+ void (*edpi_video_hs_en)(struct dsi_context *ctx, int enable);
+ void (*tear_effect_ack_en)(struct dsi_context *ctx, int enable);
+ void (*cmd_ack_request_en)(struct dsi_context *ctx, int enable);
+ void (*cmd_mode_lp_cmd_en)(struct dsi_context *ctx, int enable);
+ void (*video_mode_lp_cmd_en)(struct dsi_context *ctx, int enable);
+ void (*set_packet_header)(struct dsi_context *ctx, u8 vc, u8 type,
+ u8 wc_lsb, u8 wc_msb);
+ void (*set_packet_payload)(struct dsi_context *ctx, u32 payload);
+ u32 (*get_rx_payload)(struct dsi_context *ctx);
+ void (*bta_en)(struct dsi_context *ctx, int enable);
+ void (*eotp_rx_en)(struct dsi_context *ctx, int enable);
+ void (*eotp_tx_en)(struct dsi_context *ctx, int enable);
+ void (*ecc_rx_en)(struct dsi_context *ctx, int enable);
+ void (*crc_rx_en)(struct dsi_context *ctx, int enable);
+ bool (*is_bta_returned)(struct dsi_context *ctx);
+ bool (*is_rx_payload_fifo_full)(struct dsi_context *ctx);
+ bool (*is_rx_payload_fifo_empty)(struct dsi_context *ctx);
+ bool (*is_tx_payload_fifo_full)(struct dsi_context *ctx);
+ bool (*is_tx_payload_fifo_empty)(struct dsi_context *ctx);
+ bool (*is_tx_cmd_fifo_full)(struct dsi_context *ctx);
+ bool (*is_tx_cmd_fifo_empty)(struct dsi_context *ctx);
+ void (*datalane_hs2lp_config)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*datalane_lp2hs_config)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*clklane_hs2lp_config)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*clklane_lp2hs_config)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*max_read_time)(struct dsi_context *ctx, u16 byte_cycle);
+ void (*nc_clk_en)(struct dsi_context *ctx, int enable);
+ void (*tx_escape_division)(struct dsi_context *ctx, u8 div);
+ void (*timeout_clock_division)(struct dsi_context *ctx, u8 div);
+ void (*lp_rx_timeout)(struct dsi_context *ctx, u16 count);
+ void (*hs_tx_timeout)(struct dsi_context *ctx, u16 count);
+ void (*bta_presp_timeout)(struct dsi_context *ctx, u16 byteclk);
+ void (*lp_write_presp_timeout)(struct dsi_context *ctx, u16 byteclk);
+ void (*lp_read_presp_timeout)(struct dsi_context *ctx, u16 byteclk);
+ void (*hs_write_presp_timeout)(struct dsi_context *ctx, u16 byteclk);
+ void (*hs_read_presp_timeout)(struct dsi_context *ctx, u16 byteclk);
+ u32 (*int0_status)(struct dsi_context *ctx);
+ u32 (*int1_status)(struct dsi_context *ctx);
+ u32 (*int2_status)(struct dsi_context *ctx);
+ void (*int0_mask)(struct dsi_context *ctx, u32 mask);
+ void (*int1_mask)(struct dsi_context *ctx, u32 mask);
+ void (*int2_mask)(struct dsi_context *ctx, u32 mask);
+};
+
+struct dsi_glb_ops {
+ int (*parse_dt)(struct dsi_context *ctx,
+ struct device_node *np);
+ void (*enable)(struct dsi_context *ctx);
+ void (*disable)(struct dsi_context *ctx);
+ void (*reset)(struct dsi_context *ctx);
+ void (*power)(struct dsi_context *ctx, int enable);
+};
+
+struct sprd_dsi {
+ struct device dev;
+ struct mipi_dsi_host host;
+ struct mipi_dsi_device *slave;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ struct drm_display_mode *mode;
+ struct sprd_dphy *phy;
+ struct dsi_core_ops *core;
+ struct dsi_glb_ops *glb;
+ struct dsi_context ctx;
+};
+
+extern struct list_head dsi_core_head;
+extern struct list_head dsi_glb_head;
+
+#define dsi_core_ops_register(entry) \
+ disp_ops_register(entry, &dsi_core_head)
+#define dsi_glb_ops_register(entry) \
+ disp_ops_register(entry, &dsi_glb_head)
+
+#define dsi_core_ops_attach(str) \
+ disp_ops_attach(str, &dsi_core_head)
+#define dsi_glb_ops_attach(str) \
+ disp_ops_attach(str, &dsi_glb_head)
+
+#endif /* __SPRD_DSI_H__ */
--
2.7.4

2019-12-10 08:39:31

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 7/8] dt-bindings: display: add Unisoc's generic mipi panel bindings

From: Kevin Tang <[email protected]>

Adds generic MIPI panel support for Unisoc's display subsystem.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
.../devicetree/bindings/display/sprd/panel.txt | 110 +++++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sprd/panel.txt

diff --git a/Documentation/devicetree/bindings/display/sprd/panel.txt b/Documentation/devicetree/bindings/display/sprd/panel.txt
new file mode 100644
index 0000000..a4017af
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sprd/panel.txt
@@ -0,0 +1,110 @@
+Unisoc Generic MIPI Panel
+================================================================
+
+Required properties:
+ - compatible: must be "sprd,generic-mipi-panel"
+ - reg: panel ID
+ - #address-cells, #size-cells: should be set respectively to <1> and <0>
+ - port: video port for DPI input
+ - sprd,dsi-work-mode: the following dsi mode can be select:
+ 0: cmd mode,
+ 1: video burst mode,
+ 2: video non-burst mode with sync pulse,
+ 3: video non-burst mode with sync event
+ - sprd,dsi-lane-number: number of dsi lanes to use, default is 4
+ - sprd,dsi-color-format: data format for video stream transmission, currently
+ supports "rgb888", "rgb666", "rgb666_packed", "rgb565" and "dsc", defaults rgb888
+ - sprd,phy-bit-clock: the transmission rate of the clock lane for High-Speed,
+ the unit is Kbps, and the default value is 500Mbps
+ - sprd,phy-escape-clock: the transmission rate of the clock lane for Low-Power,
+ the unit is Kbps, and the default value is 20Mbps
+
+
+Optional properties:
+ - width-mm: see [2] for details
+ - height-mm: see [2] for details
+ - sprd,esd-check-enable: esd check function enable switch
+ - sprd,esd-check-mode: esd detection method, default is register
+ 0: register,
+ 1: TE signal
+ - sprd,esd-check-period: esd detection cycle, unit ms, default 1000ms
+ - sprd,esd-check-register: if register detection is used, this attribute must be configured
+ - sprd,esd-check-value: if register detection is used, this attribute must be configured
+ - sprd,reset-on-sequence: timing of the reset pin when the lcd power on
+ <1 5>, <0 5> means first keep high for 5ms, then keep low for 5ms
+ - sprd,reset-on-sequence: timing of the reset pin when the lcd power off
+ - sprd,use-dcs-write: bool attribute, indicating whether to use the dcs to send inital & sleep cmds,
+ default use generic
+ - sprd,initial-command: lcd initialization command set
+ - sprd,sleep-in-command: lcd suspend command set
+ - sprd,sleep-out-command: lcd resume command set
+ - display-timings: see [1] for details
+
+
+ [1] Documentation/devicetree/bindings/display/panel/display-timing.txt
+ [2] Documentation/devicetree/bindings/display/panel/panel-common.yaml
+
+Example
+-------
+
+Panel specific DT entry:
+
+ &dsi {
+ panel {
+ compatible = "sprd,generic-mipi-panel";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+
+ port {
+ reg = <1>;
+ panel_in: endpoint {
+ remote-endpoint = <&dphy_out>;
+ };
+ };
+ };
+ };
+
+ / { lcds {
+ lcd_mipi_hd: lcd_mipi_hd {
+ sprd,dsi-work-mode = <1>;
+ sprd,dsi-lane-number = <4>;
+ sprd,dsi-color-format = "rgb888";
+ sprd,phy-bit-clock = <1100000>;
+ sprd,phy-escape-clock = <20000>;
+ width-mm = <68>;
+ height-mm = <121>;
+ sprd,esd-check-enable = <0>;
+ sprd,esd-check-mode = <0>;
+ sprd,esd-check-period = <1000>;
+ sprd,esd-check-register = <0x0A>;
+ sprd,esd-check-value = <0x9C>;
+ sprd,reset-on-sequence = <1 5>, <0 5>, <1 20>;
+ sprd,reset-off-sequence = <0 5>;
+ sprd,use-dcs-write;
+ sprd,initial-command = [
+ 39 00 00 02 b0 00
+ 39 00 00 04 B3 31 00 06
+ ];
+ sprd,sleep-in-command = [
+ 13 0A 00 01 28
+ 13 78 00 01 10
+ ];
+ sprd,sleep-out-command = [
+ 13 78 00 01 11
+ 13 32 00 01 29
+ ];
+ display-timings {
+ timing0 {
+ clock-frequency = <64000000>;
+ hactive = <720>;
+ vactive = <1280>;
+ hback-porch = <31>;
+ hfront-porch = <31>;
+ vback-porch = <32>;
+ vfront-porch = <16>;
+ hsync-len = <20>;
+ vsync-len = <2>;
+ };
+ };
+ };
\ No newline at end of file
--
2.7.4

2019-12-10 08:39:31

by Kevin Tang

[permalink] [raw]
Subject: [PATCH RFC 8/8] drm/sprd: add Unisoc's drm generic mipi panel driver

From: Kevin Tang <[email protected]>

This is a generic mipi dsi panel driver, All the parameters related
to lcd panel, we are placed in the DTS to configure,
for example,lcd display timing, dpi parameter and more.

Cc: Orson Zhai <[email protected]>
Cc: Baolin Wang <[email protected]>
Cc: Chunyan Zhang <[email protected]>
Signed-off-by: Kevin Tang <[email protected]>
---
drivers/gpu/drm/sprd/Makefile | 3 +-
drivers/gpu/drm/sprd/sprd_panel.c | 778 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/sprd/sprd_panel.h | 114 ++++++
3 files changed, 894 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/sprd/sprd_panel.c
create mode 100644 drivers/gpu/drm/sprd/sprd_panel.h

diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
index 78d3ddb..30a581c 100644
--- a/drivers/gpu/drm/sprd/Makefile
+++ b/drivers/gpu/drm/sprd/Makefile
@@ -8,7 +8,8 @@ obj-y := sprd_drm.o \
sprd_gem.o \
sprd_dpu.o \
sprd_dsi.o \
- sprd_dphy.o
+ sprd_dphy.o \
+ sprd_panel.o

obj-y += disp_lib.o
obj-y += dpu/
diff --git a/drivers/gpu/drm/sprd/sprd_panel.c b/drivers/gpu/drm/sprd/sprd_panel.c
new file mode 100644
index 0000000..4a70a20
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_panel.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <linux/backlight.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+#include <video/mipi_display.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "sprd_dpu.h"
+#include "sprd_panel.h"
+#include "dsi/sprd_dsi_api.h"
+
+#define SPRD_MIPI_DSI_FMT_DSC 0xff
+static DEFINE_MUTEX(panel_lock);
+
+static const char *lcd_name;
+
+static inline struct sprd_panel *to_sprd_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct sprd_panel, base);
+}
+
+static int sprd_panel_send_cmds(struct mipi_dsi_device *dsi,
+ const void *data, int size)
+{
+ struct sprd_panel *panel;
+ const struct dsi_cmd_desc *cmds = data;
+ u16 len;
+
+ if (cmds == NULL || dsi == NULL)
+ return -EINVAL;
+
+ panel = mipi_dsi_get_drvdata(dsi);
+
+ while (size > 0) {
+ len = (cmds->wc_h << 8) | cmds->wc_l;
+
+ if (panel->info.use_dcs)
+ mipi_dsi_dcs_write_buffer(dsi, cmds->payload, len);
+ else
+ mipi_dsi_generic_write(dsi, cmds->payload, len);
+
+ if (cmds->wait)
+ msleep(cmds->wait);
+ cmds = (const struct dsi_cmd_desc *)(cmds->payload + len);
+ size -= (len + 4);
+ }
+
+ return 0;
+}
+
+static int sprd_panel_unprepare(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+ struct gpio_timing *timing;
+ int items, i;
+
+ DRM_INFO("%s()\n", __func__);
+
+ if (panel->info.avee_gpio) {
+ gpiod_direction_output(panel->info.avee_gpio, 0);
+ mdelay(5);
+ }
+
+ if (panel->info.avdd_gpio) {
+ gpiod_direction_output(panel->info.avdd_gpio, 0);
+ mdelay(5);
+ }
+
+ if (panel->info.reset_gpio) {
+ items = panel->info.rst_off_seq.items;
+ timing = panel->info.rst_off_seq.timing;
+ for (i = 0; i < items; i++) {
+ gpiod_direction_output(panel->info.reset_gpio,
+ timing[i].level);
+ mdelay(timing[i].delay);
+ }
+ }
+
+ regulator_disable(panel->supply);
+
+ return 0;
+}
+
+static int sprd_panel_prepare(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+ struct gpio_timing *timing;
+ int items, i, ret;
+
+ DRM_INFO("%s()\n", __func__);
+
+ ret = regulator_enable(panel->supply);
+ if (ret < 0) {
+ DRM_ERROR("enable lcd regulator failed\n");
+ return ret;
+ }
+
+ if (panel->info.avdd_gpio) {
+ gpiod_direction_output(panel->info.avdd_gpio, 1);
+ mdelay(5);
+ }
+
+ if (panel->info.avee_gpio) {
+ gpiod_direction_output(panel->info.avee_gpio, 1);
+ mdelay(5);
+ }
+
+ if (panel->info.reset_gpio) {
+ items = panel->info.rst_on_seq.items;
+ timing = panel->info.rst_on_seq.timing;
+ for (i = 0; i < items; i++) {
+ gpiod_direction_output(panel->info.reset_gpio,
+ timing[i].level);
+ mdelay(timing[i].delay);
+ }
+ }
+
+ return 0;
+}
+
+static int sprd_panel_disable(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+
+ DRM_INFO("%s()\n", __func__);
+
+ mutex_lock(&panel_lock);
+ /*
+ * FIXME:
+ * The cancel work should be executed before DPU stop,
+ * otherwise the esd check will be failed if the DPU
+ * stopped in video mode and the DSI has not change to
+ * CMD mode yet. Since there is no VBLANK timing for
+ * LP cmd transmission.
+ */
+ if (panel->esd_work_pending) {
+ cancel_delayed_work_sync(&panel->esd_work);
+ panel->esd_work_pending = false;
+ }
+
+ if (panel->backlight) {
+ panel->backlight->props.power = FB_BLANK_POWERDOWN;
+ panel->backlight->props.state |= BL_CORE_FBBLANK;
+ backlight_update_status(panel->backlight);
+ }
+
+ sprd_panel_send_cmds(panel->slave,
+ panel->info.cmds[CMD_CODE_SLEEP_IN],
+ panel->info.cmds_len[CMD_CODE_SLEEP_IN]);
+
+ panel->is_enabled = false;
+ mutex_unlock(&panel_lock);
+
+ return 0;
+}
+
+static int sprd_panel_enable(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+
+ DRM_INFO("%s()\n", __func__);
+
+ mutex_lock(&panel_lock);
+ sprd_panel_send_cmds(panel->slave,
+ panel->info.cmds[CMD_CODE_INIT],
+ panel->info.cmds_len[CMD_CODE_INIT]);
+
+ if (panel->backlight) {
+ panel->backlight->props.power = FB_BLANK_UNBLANK;
+ panel->backlight->props.state &= ~BL_CORE_FBBLANK;
+ backlight_update_status(panel->backlight);
+ }
+
+ if (panel->info.esd_check_en) {
+ schedule_delayed_work(&panel->esd_work,
+ msecs_to_jiffies(1000));
+ panel->esd_work_pending = true;
+ }
+
+ panel->is_enabled = true;
+ mutex_unlock(&panel_lock);
+
+ return 0;
+}
+
+static int sprd_panel_get_modes(struct drm_panel *p)
+{
+ struct drm_display_mode *mode;
+ struct sprd_panel *panel = to_sprd_panel(p);
+ struct device_node *np = panel->slave->dev.of_node;
+ u32 surface_width = 0, surface_height = 0;
+ int i, mode_count = 0;
+
+ DRM_INFO("%s()\n", __func__);
+ mode = drm_mode_duplicate(p->drm, &panel->info.mode);
+ if (!mode) {
+ DRM_ERROR("failed to alloc mode %s\n", panel->info.mode.name);
+ return 0;
+ }
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(p->connector, mode);
+ mode_count++;
+
+ for (i = 1; i < panel->info.num_buildin_modes; i++) {
+ mode = drm_mode_duplicate(p->drm,
+ &(panel->info.buildin_modes[i]));
+ if (!mode) {
+ DRM_ERROR("failed to alloc mode %s\n",
+ panel->info.buildin_modes[i].name);
+ return 0;
+ }
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_DEFAULT;
+ drm_mode_probed_add(p->connector, mode);
+ mode_count++;
+ }
+
+ of_property_read_u32(np, "sprd,surface-width", &surface_width);
+ of_property_read_u32(np, "sprd,surface-height", &surface_height);
+ if (surface_width && surface_height) {
+ struct videomode vm = {};
+
+ vm.hactive = surface_width;
+ vm.vactive = surface_height;
+ vm.pixelclock = surface_width * surface_height * 60;
+
+ mode = drm_mode_create(p->drm);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_BUILTIN |
+ DRM_MODE_TYPE_CRTC_C;
+ mode->vrefresh = 60;
+ drm_display_mode_from_videomode(&vm, mode);
+ drm_mode_probed_add(p->connector, mode);
+ mode_count++;
+ }
+
+ p->connector->display_info.width_mm = panel->info.mode.width_mm;
+ p->connector->display_info.height_mm = panel->info.mode.height_mm;
+
+ return mode_count;
+}
+
+static const struct drm_panel_funcs sprd_panel_funcs = {
+ .get_modes = sprd_panel_get_modes,
+ .enable = sprd_panel_enable,
+ .disable = sprd_panel_disable,
+ .prepare = sprd_panel_prepare,
+ .unprepare = sprd_panel_unprepare,
+};
+
+static int sprd_panel_esd_check(struct sprd_panel *panel)
+{
+ struct panel_info *info = &panel->info;
+ u8 read_val = 0;
+
+ /* FIXME: we should enable HS cmd tx here */
+ mipi_dsi_set_maximum_return_packet_size(panel->slave, 1);
+ mipi_dsi_dcs_read(panel->slave, info->esd_check_reg,
+ &read_val, 1);
+
+ /*
+ * TODO:
+ * Should we support multi-registers check in the future?
+ */
+ if (read_val != info->esd_check_val) {
+ DRM_ERROR("esd check failed, read value = 0x%02x\n",
+ read_val);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sprd_panel_te_check(struct sprd_panel *panel)
+{
+ static int te_wq_inited;
+ struct sprd_dpu *dpu;
+ int ret;
+ bool irq_occur;
+
+ if (!panel->base.connector ||
+ !panel->base.connector->encoder ||
+ !panel->base.connector->encoder->crtc) {
+ return 0;
+ }
+
+ dpu = container_of(panel->base.connector->encoder->crtc,
+ struct sprd_dpu, crtc);
+
+ if (!te_wq_inited) {
+ init_waitqueue_head(&dpu->ctx.te_wq);
+ te_wq_inited = 1;
+ dpu->ctx.evt_te = false;
+ DRM_INFO("%s init te waitqueue\n", __func__);
+ }
+
+ /* DPU TE irq maybe enabled in kernel */
+ if (!dpu->ctx.is_inited)
+ return 0;
+
+ dpu->ctx.te_check_en = true;
+
+ /* wait for TE interrupt */
+ ret = wait_event_interruptible_timeout(dpu->ctx.te_wq,
+ dpu->ctx.evt_te, msecs_to_jiffies(500));
+ if (!ret) {
+ /* double check TE interrupt through dpu_int_raw register */
+ if (dpu->core && dpu->core->check_raw_int) {
+ irq_occur = dpu->core->check_raw_int(&dpu->ctx,
+ DISPC_INT_TE_MASK);
+ if (!irq_occur) {
+ DRM_ERROR("TE esd timeout.\n");
+ ret = -ETIMEDOUT;
+ } else
+ DRM_WARN("TE occur, but isr schedule delay\n");
+ } else {
+ DRM_ERROR("TE esd timeout.\n");
+ ret = -ETIMEDOUT;
+ }
+ }
+
+ dpu->ctx.te_check_en = false;
+ dpu->ctx.evt_te = false;
+
+ return ret < 0 ? ret : 0;
+}
+
+static void sprd_panel_esd_work_func(struct work_struct *work)
+{
+ struct sprd_panel *panel = container_of(work, struct sprd_panel,
+ esd_work.work);
+ struct panel_info *info = &panel->info;
+ int ret;
+
+ if (info->esd_check_mode == ESD_MODE_REG_CHECK)
+ ret = sprd_panel_esd_check(panel);
+ else if (info->esd_check_mode == ESD_MODE_TE_CHECK)
+ ret = sprd_panel_te_check(panel);
+ else {
+ DRM_ERROR("unknown esd check mode:%d\n", info->esd_check_mode);
+ return;
+ }
+
+ if (ret && panel->base.connector && panel->base.connector->encoder) {
+ const struct drm_encoder_helper_funcs *funcs;
+ struct drm_encoder *encoder;
+
+ encoder = panel->base.connector->encoder;
+ funcs = encoder->helper_private;
+ panel->esd_work_pending = false;
+
+ if (encoder->crtc && encoder->crtc->state &&
+ !encoder->crtc->state->active) {
+ DRM_INFO("skip esd recovery during panel suspend\n");
+ return;
+ }
+
+ DRM_INFO("====== esd recovery start ========\n");
+ funcs->disable(encoder);
+
+ if (!encoder->crtc->state->active) {
+ DRM_INFO("skip esd recovery if panel suspend\n");
+ return;
+ }
+ funcs->enable(encoder);
+ DRM_INFO("======= esd recovery end =========\n");
+ } else
+ schedule_delayed_work(&panel->esd_work,
+ msecs_to_jiffies(info->esd_check_period));
+}
+
+static int sprd_panel_gpio_request(struct device *dev,
+ struct sprd_panel *panel)
+{
+ panel->info.avdd_gpio = devm_gpiod_get_optional(dev,
+ "avdd", GPIOD_ASIS);
+ if (IS_ERR_OR_NULL(panel->info.avdd_gpio))
+ DRM_WARN("can't get panel avdd gpio: %ld\n",
+ PTR_ERR(panel->info.avdd_gpio));
+
+ panel->info.avee_gpio = devm_gpiod_get_optional(dev,
+ "avee", GPIOD_ASIS);
+ if (IS_ERR_OR_NULL(panel->info.avee_gpio))
+ DRM_WARN("can't get panel avee gpio: %ld\n",
+ PTR_ERR(panel->info.avee_gpio));
+
+ panel->info.reset_gpio = devm_gpiod_get_optional(dev,
+ "reset", GPIOD_ASIS);
+ if (IS_ERR_OR_NULL(panel->info.reset_gpio))
+ DRM_WARN("can't get panel reset gpio: %ld\n",
+ PTR_ERR(panel->info.reset_gpio));
+
+ return 0;
+}
+
+static int of_parse_reset_seq(struct device_node *np,
+ struct panel_info *info)
+{
+ struct property *prop;
+ int bytes, rc;
+ u32 *p;
+
+ prop = of_find_property(np, "sprd,reset-on-sequence", &bytes);
+ if (!prop) {
+ DRM_ERROR("sprd,reset-on-sequence property not found\n");
+ return -EINVAL;
+ }
+
+ p = kzalloc(bytes, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ rc = of_property_read_u32_array(np, "sprd,reset-on-sequence",
+ p, bytes / 4);
+ if (rc) {
+ DRM_ERROR("parse sprd,reset-on-sequence failed\n");
+ kfree(p);
+ return rc;
+ }
+
+ info->rst_on_seq.items = bytes / 8;
+ info->rst_on_seq.timing = (struct gpio_timing *)p;
+
+ prop = of_find_property(np, "sprd,reset-off-sequence", &bytes);
+ if (!prop) {
+ DRM_ERROR("sprd,reset-off-sequence property not found\n");
+ return -EINVAL;
+ }
+
+ p = kzalloc(bytes, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ rc = of_property_read_u32_array(np, "sprd,reset-off-sequence",
+ p, bytes / 4);
+ if (rc) {
+ DRM_ERROR("parse sprd,reset-off-sequence failed\n");
+ kfree(p);
+ return rc;
+ }
+
+ info->rst_off_seq.items = bytes / 8;
+ info->rst_off_seq.timing = (struct gpio_timing *)p;
+
+ return 0;
+}
+
+static int of_parse_buildin_modes(struct panel_info *info,
+ struct device_node *lcd_node)
+{
+ int i, rc, num_timings;
+ struct device_node *timings_np;
+
+
+ timings_np = of_get_child_by_name(lcd_node, "display-timings");
+ if (!timings_np) {
+ DRM_ERROR("%s: can not find display-timings node\n",
+ lcd_node->name);
+ return -ENODEV;
+ }
+
+ num_timings = of_get_child_count(timings_np);
+ if (num_timings == 0) {
+ /* should never happen, as entry was already found above */
+ DRM_ERROR("%s: no timings specified\n", lcd_node->name);
+ goto done;
+ }
+
+ info->buildin_modes = kzalloc(sizeof(struct drm_display_mode) *
+ num_timings, GFP_KERNEL);
+
+ for (i = 0; i < num_timings; i++) {
+ rc = of_get_drm_display_mode(lcd_node,
+ &info->buildin_modes[i], NULL, i);
+ if (rc) {
+ DRM_ERROR("get display timing failed\n");
+ goto entryfail;
+ }
+
+ info->buildin_modes[i].width_mm = info->mode.width_mm;
+ info->buildin_modes[i].height_mm = info->mode.height_mm;
+ info->buildin_modes[i].vrefresh = info->mode.vrefresh;
+ }
+ info->num_buildin_modes = num_timings;
+ DRM_INFO("info->num_buildin_modes = %d\n", num_timings);
+ goto done;
+
+entryfail:
+ kfree(info->buildin_modes);
+done:
+ of_node_put(timings_np);
+
+ return 0;
+}
+
+static int sprd_panel_parse_dt(struct device_node *np, struct sprd_panel *panel)
+{
+ u32 val;
+ struct device_node *lcd_node;
+ struct panel_info *info = &panel->info;
+ int bytes, rc;
+ const void *p;
+ const char *str;
+ char lcd_path[60];
+
+ sprintf(lcd_path, "/lcds/%s", lcd_name);
+ lcd_node = of_find_node_by_path(lcd_path);
+ if (!lcd_node) {
+ DRM_ERROR("%pOF: could not find %s node\n", np, lcd_name);
+ return -ENODEV;
+ }
+ info->of_node = lcd_node;
+
+ rc = of_property_read_u32(lcd_node, "sprd,dsi-work-mode", &val);
+ if (!rc) {
+ if (val == SPRD_DSI_MODE_CMD)
+ info->mode_flags = 0;
+ else if (val == SPRD_DSI_MODE_VIDEO_BURST)
+ info->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST;
+ else if (val == SPRD_DSI_MODE_VIDEO_SYNC_PULSE)
+ info->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ else if (val == SPRD_DSI_MODE_VIDEO_SYNC_EVENT)
+ info->mode_flags = MIPI_DSI_MODE_VIDEO;
+ } else {
+ DRM_ERROR("dsi work mode is not found! use video mode\n");
+ info->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST;
+ }
+
+ if (of_property_read_bool(lcd_node, "sprd,dsi-non-continuous-clock"))
+ info->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+ rc = of_property_read_u32(lcd_node, "sprd,dsi-lane-number", &val);
+ if (!rc)
+ info->lanes = val;
+ else
+ info->lanes = 4;
+
+ rc = of_property_read_string(lcd_node, "sprd,dsi-color-format", &str);
+ if (rc)
+ info->format = MIPI_DSI_FMT_RGB888;
+ else if (!strcmp(str, "rgb888"))
+ info->format = MIPI_DSI_FMT_RGB888;
+ else if (!strcmp(str, "rgb666"))
+ info->format = MIPI_DSI_FMT_RGB666;
+ else if (!strcmp(str, "rgb666_packed"))
+ info->format = MIPI_DSI_FMT_RGB666_PACKED;
+ else if (!strcmp(str, "rgb565"))
+ info->format = MIPI_DSI_FMT_RGB565;
+ else if (!strcmp(str, "dsc"))
+ info->format = SPRD_MIPI_DSI_FMT_DSC;
+ else
+ DRM_ERROR("dsi-color-format (%s) is not supported\n", str);
+
+ rc = of_property_read_u32(lcd_node, "width-mm", &val);
+ if (!rc)
+ info->mode.width_mm = val;
+ else
+ info->mode.width_mm = 68;
+
+ rc = of_property_read_u32(lcd_node, "height-mm", &val);
+ if (!rc)
+ info->mode.height_mm = val;
+ else
+ info->mode.height_mm = 121;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-enable", &val);
+ if (!rc)
+ info->esd_check_en = val;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-mode", &val);
+ if (!rc)
+ info->esd_check_mode = val;
+ else
+ info->esd_check_mode = 1;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-period", &val);
+ if (!rc)
+ info->esd_check_period = val;
+ else
+ info->esd_check_period = 1000;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-register", &val);
+ if (!rc)
+ info->esd_check_reg = val;
+ else
+ info->esd_check_reg = 0x0A;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-value", &val);
+ if (!rc)
+ info->esd_check_val = val;
+ else
+ info->esd_check_val = 0x9C;
+
+ if (of_property_read_bool(lcd_node, "sprd,use-dcs-write"))
+ info->use_dcs = true;
+ else
+ info->use_dcs = false;
+
+ rc = of_parse_reset_seq(lcd_node, info);
+ if (rc)
+ DRM_ERROR("parse lcd reset sequence failed\n");
+
+ p = of_get_property(lcd_node, "sprd,initial-command", &bytes);
+ if (p) {
+ info->cmds[CMD_CODE_INIT] = p;
+ info->cmds_len[CMD_CODE_INIT] = bytes;
+ } else
+ DRM_ERROR("can't find sprd,initial-command property\n");
+
+ p = of_get_property(lcd_node, "sprd,sleep-in-command", &bytes);
+ if (p) {
+ info->cmds[CMD_CODE_SLEEP_IN] = p;
+ info->cmds_len[CMD_CODE_SLEEP_IN] = bytes;
+ } else
+ DRM_ERROR("can't find sprd,sleep-in-command property\n");
+
+ p = of_get_property(lcd_node, "sprd,sleep-out-command", &bytes);
+ if (p) {
+ info->cmds[CMD_CODE_SLEEP_OUT] = p;
+ info->cmds_len[CMD_CODE_SLEEP_OUT] = bytes;
+ } else
+ DRM_ERROR("can't find sprd,sleep-out-command property\n");
+
+ rc = of_get_drm_display_mode(lcd_node, &info->mode, 0,
+ OF_USE_NATIVE_MODE);
+ if (rc) {
+ DRM_ERROR("get display timing failed\n");
+ return rc;
+ }
+
+ info->mode.vrefresh = drm_mode_vrefresh(&info->mode);
+ of_parse_buildin_modes(info, lcd_node);
+
+ return 0;
+}
+
+static int sprd_panel_probe(struct mipi_dsi_device *slave)
+{
+ int ret;
+ struct sprd_panel *panel;
+ struct device_node *bl_node;
+
+ panel = devm_kzalloc(&slave->dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ bl_node = of_parse_phandle(slave->dev.of_node,
+ "sprd,backlight", 0);
+ if (bl_node) {
+ panel->backlight = of_find_backlight_by_node(bl_node);
+ of_node_put(bl_node);
+
+ if (panel->backlight) {
+ panel->backlight->props.state &= ~BL_CORE_FBBLANK;
+ panel->backlight->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(panel->backlight);
+ } else {
+ DRM_WARN("backlight is not ready, panel probe deferred\n");
+ return -EPROBE_DEFER;
+ }
+ } else
+ DRM_WARN("backlight node not found\n");
+
+ panel->supply = devm_regulator_get(&slave->dev, "power");
+ if (IS_ERR(panel->supply)) {
+ if (PTR_ERR(panel->supply) == -EPROBE_DEFER)
+ DRM_ERROR("regulator driver not initialized, probe deffer\n");
+ else
+ DRM_ERROR("can't get regulator: %ld\n", PTR_ERR(panel->supply));
+
+ return PTR_ERR(panel->supply);
+ }
+
+ INIT_DELAYED_WORK(&panel->esd_work, sprd_panel_esd_work_func);
+
+ ret = sprd_panel_parse_dt(slave->dev.of_node, panel);
+ if (ret) {
+ DRM_ERROR("parse panel info failed\n");
+ return ret;
+ }
+
+ ret = sprd_panel_gpio_request(&slave->dev, panel);
+ if (ret) {
+ DRM_WARN("gpio is not ready, panel probe deferred\n");
+ return -EPROBE_DEFER;
+ }
+
+ drm_panel_init(&panel->base, &panel->dev,
+ &sprd_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_add(&panel->base);
+ if (ret) {
+ DRM_ERROR("drm_panel_add() failed\n");
+ return ret;
+ }
+
+ slave->lanes = panel->info.lanes;
+ slave->format = panel->info.format;
+ slave->mode_flags = panel->info.mode_flags;
+
+ ret = mipi_dsi_attach(slave);
+ if (ret) {
+ DRM_ERROR("failed to attach dsi panel to host\n");
+ drm_panel_remove(&panel->base);
+ return ret;
+ }
+ panel->slave = slave;
+
+ mipi_dsi_set_drvdata(slave, panel);
+
+ /*
+ * FIXME:
+ * The esd check work should not be scheduled in probe
+ * function. It should be scheduled in the enable()
+ * callback function. But the dsi encoder will not call
+ * drm_panel_enable() the first time in encoder_enable().
+ */
+ if (panel->info.esd_check_en) {
+ schedule_delayed_work(&panel->esd_work,
+ msecs_to_jiffies(2000));
+ panel->esd_work_pending = true;
+ }
+
+ panel->is_enabled = true;
+
+ DRM_INFO("panel driver probe success\n");
+
+ return 0;
+}
+
+static int sprd_panel_remove(struct mipi_dsi_device *slave)
+{
+ struct sprd_panel *panel = mipi_dsi_get_drvdata(slave);
+ int ret;
+
+ DRM_INFO("%s()\n", __func__);
+
+ sprd_panel_disable(&panel->base);
+ sprd_panel_unprepare(&panel->base);
+
+ ret = mipi_dsi_detach(slave);
+ if (ret < 0)
+ DRM_ERROR("failed to detach from DSI host: %d\n", ret);
+
+ drm_panel_detach(&panel->base);
+ drm_panel_remove(&panel->base);
+
+ return 0;
+}
+
+static const struct of_device_id panel_of_match[] = {
+ { .compatible = "sprd,generic-mipi-panel", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+static struct mipi_dsi_driver sprd_panel_driver = {
+ .driver = {
+ .name = "sprd-mipi-panel-drv",
+ .of_match_table = panel_of_match,
+ },
+ .probe = sprd_panel_probe,
+ .remove = sprd_panel_remove,
+};
+module_mipi_dsi_driver(sprd_panel_driver);
+
+MODULE_AUTHOR("Leon He <[email protected]>");
+MODULE_AUTHOR("Kevin Tang <[email protected]>");
+MODULE_DESCRIPTION("Unisoc MIPI DSI Panel Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_panel.h b/drivers/gpu/drm/sprd/sprd_panel.h
new file mode 100644
index 0000000..216cd4b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_panel.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _SPRD_PANEL_H_
+#define _SPRD_PANEL_H_
+
+#include <linux/backlight.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+
+enum {
+ CMD_CODE_INIT = 0,
+ CMD_CODE_SLEEP_IN,
+ CMD_CODE_SLEEP_OUT,
+ CMD_OLED_BRIGHTNESS,
+ CMD_OLED_REG_LOCK,
+ CMD_OLED_REG_UNLOCK,
+ CMD_CODE_RESERVED0,
+ CMD_CODE_RESERVED1,
+ CMD_CODE_RESERVED2,
+ CMD_CODE_RESERVED3,
+ CMD_CODE_RESERVED4,
+ CMD_CODE_RESERVED5,
+ CMD_CODE_MAX,
+};
+
+enum {
+ SPRD_DSI_MODE_CMD = 0,
+ SPRD_DSI_MODE_VIDEO_BURST,
+ SPRD_DSI_MODE_VIDEO_SYNC_PULSE,
+ SPRD_DSI_MODE_VIDEO_SYNC_EVENT,
+};
+
+enum {
+ ESD_MODE_REG_CHECK,
+ ESD_MODE_TE_CHECK,
+};
+
+struct dsi_cmd_desc {
+ u8 data_type;
+ u8 wait;
+ u8 wc_h;
+ u8 wc_l;
+ u8 payload[];
+};
+
+struct gpio_timing {
+ u32 level;
+ u32 delay;
+};
+
+struct reset_sequence {
+ u32 items;
+ struct gpio_timing *timing;
+};
+
+struct panel_info {
+ /* common parameters */
+ struct device_node *of_node;
+ struct drm_display_mode mode;
+ struct drm_display_mode *buildin_modes;
+ int num_buildin_modes;
+ struct gpio_desc *avdd_gpio;
+ struct gpio_desc *avee_gpio;
+ struct gpio_desc *reset_gpio;
+ struct reset_sequence rst_on_seq;
+ struct reset_sequence rst_off_seq;
+ const void *cmds[CMD_CODE_MAX];
+ int cmds_len[CMD_CODE_MAX];
+
+ /* esd check parameters*/
+ bool esd_check_en;
+ u8 esd_check_mode;
+ u16 esd_check_period;
+ u32 esd_check_reg;
+ u32 esd_check_val;
+
+ /* MIPI DSI specific parameters */
+ u32 format;
+ u32 lanes;
+ u32 mode_flags;
+ bool use_dcs;
+};
+
+struct sprd_panel {
+ struct device dev;
+ struct drm_panel base;
+ struct mipi_dsi_device *slave;
+ struct panel_info info;
+ struct backlight_device *backlight;
+ struct regulator *supply;
+ struct delayed_work esd_work;
+ bool esd_work_pending;
+ bool is_enabled;
+};
+
+struct sprd_oled {
+ struct backlight_device *bdev;
+ struct sprd_panel *panel;
+ struct dsi_cmd_desc *cmds[255];
+ int cmd_len;
+ int cmds_total;
+ int max_level;
+};
+
+#endif
--
2.7.4

2019-12-10 10:34:09

by Thomas Zimmermann

[permalink] [raw]
Subject: Re: [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master

Hi

Am 10.12.19 um 09:36 schrieb Kevin Tang:
> From: Kevin Tang <[email protected]>
>
> Adds drm support for the Unisoc's display subsystem.
>
> This is drm device and gem driver. This driver provides support for the
> Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/sprd/Kconfig | 14 ++
> drivers/gpu/drm/sprd/Makefile | 8 ++
> drivers/gpu/drm/sprd/sprd_drm.c | 287 ++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_drm.h | 19 +++
> drivers/gpu/drm/sprd/sprd_gem.c | 178 +++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_gem.h | 30 +++++

The GEM implementation looks like DRM's CMA helpers. Can you not use CMA
helpers instead?

> 8 files changed, 539 insertions(+)
> create mode 100644 drivers/gpu/drm/sprd/Kconfig
> create mode 100644 drivers/gpu/drm/sprd/Makefile
> create mode 100644 drivers/gpu/drm/sprd/sprd_drm.c
> create mode 100644 drivers/gpu/drm/sprd/sprd_drm.h
> create mode 100644 drivers/gpu/drm/sprd/sprd_gem.c
> create mode 100644 drivers/gpu/drm/sprd/sprd_gem.h
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index bfdadc3..cead12c 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -387,6 +387,8 @@ source "drivers/gpu/drm/aspeed/Kconfig"
>
> source "drivers/gpu/drm/mcde/Kconfig"
>
> +source "drivers/gpu/drm/sprd/Kconfig"
> +
> # Keep legacy drivers last
>
> menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 9f1c7c4..85ca211 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -122,3 +122,4 @@ obj-$(CONFIG_DRM_LIMA) += lima/
> obj-$(CONFIG_DRM_PANFROST) += panfrost/
> obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
> obj-$(CONFIG_DRM_MCDE) += mcde/
> +obj-$(CONFIG_DRM_SPRD) += sprd/
> diff --git a/drivers/gpu/drm/sprd/Kconfig b/drivers/gpu/drm/sprd/Kconfig
> new file mode 100644
> index 0000000..79f286b
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/Kconfig
> @@ -0,0 +1,14 @@
> +config DRM_SPRD
> + tristate "DRM Support for Unisoc SoCs Platform"
> + depends on ARCH_SPRD
> + depends on DRM && OF
> + select DRM_KMS_HELPER
> + select DRM_GEM_CMA_HELPER
> + select DRM_KMS_CMA_HELPER
> + select DRM_MIPI_DSI
> + select DRM_PANEL
> + select VIDEOMODE_HELPERS
> + select BACKLIGHT_CLASS_DEVICE
> + help
> + Choose this option if you have a Unisoc chipsets.
> + If M is selected the module will be called sprd-drm.
> \ No newline at end of file
> diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
> new file mode 100644
> index 0000000..df0b316
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +ccflags-y += -Iinclude/drm
> +
> +subdir-ccflags-y += -I$(src)
> +
> +obj-y := sprd_drm.o \
> + sprd_gem.o
> \ No newline at end of file
> diff --git a/drivers/gpu/drm/sprd/sprd_drm.c b/drivers/gpu/drm/sprd/sprd_drm.c
> new file mode 100644
> index 0000000..ec16fee
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_drm.c
> @@ -0,0 +1,287 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#include <linux/component.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "sprd_drm.h"
> +#include "sprd_gem.h"
> +
> +#define DRIVER_NAME "sprd"
> +#define DRIVER_DESC "Spreadtrum SoCs' DRM Driver"
> +#define DRIVER_DATE "20180501"
> +#define DRIVER_MAJOR 1
> +#define DRIVER_MINOR 0
> +
> +static const struct drm_mode_config_helper_funcs sprd_drm_mode_config_helper = {
> + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
> +};
> +
> +static const struct drm_mode_config_funcs sprd_drm_mode_config_funcs = {
> + .fb_create = drm_gem_fb_create,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static void sprd_drm_mode_config_init(struct drm_device *drm)
> +{
> + drm_mode_config_init(drm);
> +
> + drm->mode_config.min_width = 0;
> + drm->mode_config.min_height = 0;
> + drm->mode_config.max_width = 8192;
> + drm->mode_config.max_height = 8192;
> + drm->mode_config.allow_fb_modifiers = true;
> +
> + drm->mode_config.funcs = &sprd_drm_mode_config_funcs;
> + drm->mode_config.helper_private = &sprd_drm_mode_config_helper;
> +}
> +
> +static const struct file_operations sprd_drm_fops = {
> + .owner = THIS_MODULE,
> + .open = drm_open,
> + .release = drm_release,
> + .unlocked_ioctl = drm_ioctl,
> + .compat_ioctl = drm_compat_ioctl,
> + .poll = drm_poll,
> + .read = drm_read,
> + .llseek = no_llseek,
> + .mmap = sprd_gem_cma_mmap,
> +};
> +
> +static struct drm_driver sprd_drm_drv = {
> + .driver_features = DRIVER_GEM | DRIVER_MODESET |
> + DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
> + .fops = &sprd_drm_fops,
> +
> + .gem_vm_ops = &drm_gem_cma_vm_ops,
> + .gem_free_object_unlocked = sprd_gem_free_object,
> + .dumb_create = sprd_gem_cma_dumb_create,
> +
> + .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> + .gem_prime_import = drm_gem_prime_import,
> + .gem_prime_import_sg_table = sprd_gem_prime_import_sg_table,
> +
> + .name = DRIVER_NAME,
> + .desc = DRIVER_DESC,
> + .date = DRIVER_DATE,
> + .major = DRIVER_MAJOR,
> + .minor = DRIVER_MINOR,
> +};
> +
> +static int sprd_drm_bind(struct device *dev)
> +{
> + struct drm_device *drm;
> + struct sprd_drm *sprd;
> + int err;
> +
> + drm = drm_dev_alloc(&sprd_drm_drv, dev);
> + if (IS_ERR(drm))
> + return PTR_ERR(drm);
> +
> + dev_set_drvdata(dev, drm);
> +
> + sprd = devm_kzalloc(drm->dev, sizeof(*sprd), GFP_KERNEL);
> + if (!sprd) {
> + err = -ENOMEM;
> + goto err_free_drm;
> + }
> + drm->dev_private = sprd;
> +
> + sprd_drm_mode_config_init(drm);
> +
> + /* bind and init sub drivers */
> + err = component_bind_all(drm->dev, drm);
> + if (err) {
> + DRM_ERROR("failed to bind all component.\n");
> + goto err_dc_cleanup;
> + }
> +
> + /* vblank init */
> + err = drm_vblank_init(drm, drm->mode_config.num_crtc);
> + if (err) {
> + DRM_ERROR("failed to initialize vblank.\n");
> + goto err_unbind_all;
> + }
> + /* with irq_enabled = true, we can use the vblank feature. */
> + drm->irq_enabled = true;
> +
> + /* reset all the states of crtc/plane/encoder/connector */
> + drm_mode_config_reset(drm);
> +
> + /* init kms poll for handling hpd */
> + drm_kms_helper_poll_init(drm);
> +
> + err = drm_dev_register(drm, 0);
> + if (err < 0)
> + goto err_kms_helper_poll_fini;
> +
> + return 0;
> +
> +err_kms_helper_poll_fini:
> + drm_kms_helper_poll_fini(drm);
> +err_unbind_all:
> + component_unbind_all(drm->dev, drm);
> +err_dc_cleanup:
> + drm_mode_config_cleanup(drm);
> +err_free_drm:
> + drm_dev_put(drm);
> + return err;
> +}
> +
> +static void sprd_drm_unbind(struct device *dev)
> +{
> + drm_put_dev(dev_get_drvdata(dev));
> +}
> +
> +static const struct component_master_ops sprd_drm_component_ops = {
> + .bind = sprd_drm_bind,
> + .unbind = sprd_drm_unbind,
> +};
> +
> +static int compare_of(struct device *dev, void *data)
> +{
> + struct device_node *np = data;
> +
> + DRM_DEBUG("compare %s\n", np->full_name);
> +
> + return dev->of_node == np;
> +}
> +
> +static int sprd_drm_component_probe(struct device *dev,
> + const struct component_master_ops *m_ops)
> +{
> + struct device_node *ep, *port, *remote;
> + struct component_match *match = NULL;
> + int i;
> +
> + if (!dev->of_node)
> + return -EINVAL;
> +
> + /*
> + * Bind the crtc's ports first, so that drm_of_find_possible_crtcs()
> + * called from encoder's .bind callbacks works as expected
> + */
> + for (i = 0; ; i++) {
> + port = of_parse_phandle(dev->of_node, "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;
> + }
> +
> + if (!match) {
> + dev_err(dev, "no available port\n");
> + return -ENODEV;
> + }
> +
> + /*
> + * For bound crtcs, bind the encoders attached to their remote endpoint
> + */
> + for (i = 0; ; i++) {
> + port = of_parse_phandle(dev->of_node, "ports", i);
> + if (!port)
> + break;
> +
> + if (!of_device_is_available(port->parent)) {
> + of_node_put(port);
> + continue;
> + }
> +
> + 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);
> + }
> + of_node_put(port);
> + }
> +
> + return component_master_add_with_match(dev, m_ops, match);
> +}
> +
> +static int sprd_drm_probe(struct platform_device *pdev)
> +{
> + int ret;
> +
> + ret = dma_set_mask_and_coherent(&pdev->dev, ~0);
> + if (ret)
> + DRM_ERROR("dma_set_mask_and_coherent failed (%d)\n", ret);
> +
> + return sprd_drm_component_probe(&pdev->dev, &sprd_drm_component_ops);
> +}
> +
> +static int sprd_drm_remove(struct platform_device *pdev)
> +{
> + component_master_del(&pdev->dev, &sprd_drm_component_ops);
> + return 0;
> +}
> +
> +static void sprd_drm_shutdown(struct platform_device *pdev)
> +{
> + struct drm_device *drm = platform_get_drvdata(pdev);
> +
> + if (!drm) {
> + DRM_WARN("drm device is not available, no shutdown\n");
> + return;
> + }
> +
> + drm_atomic_helper_shutdown(drm);
> +}
> +
> +static const struct of_device_id sprd_drm_match_table[] = {
> + { .compatible = "sprd,display-subsystem",},
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, sprd_drm_match_table);
> +
> +static struct platform_driver sprd_drm_driver = {
> + .probe = sprd_drm_probe,
> + .remove = sprd_drm_remove,
> + .shutdown = sprd_drm_shutdown,
> + .driver = {
> + .name = "sprd-drm-drv",
> + .of_match_table = sprd_drm_match_table,
> + },
> +};
> +
> +module_platform_driver(sprd_drm_driver);
> +
> +MODULE_AUTHOR("Leon He <[email protected]>");
> +MODULE_AUTHOR("Kevin Tang <[email protected]>");
> +MODULE_DESCRIPTION("Unisoc DRM KMS Master Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/sprd/sprd_drm.h b/drivers/gpu/drm/sprd/sprd_drm.h
> new file mode 100644
> index 0000000..e840e65
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef _SPRD_DRM_H_
> +#define _SPRD_DRM_H_
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_print.h>
> +
> +struct sprd_drm {
> + struct drm_device *drm;
> + struct drm_atomic_state *state;
> + struct device *dpu_dev;
> + struct device *gsp_dev;
> +};
> +
> +#endif /* _SPRD_DRM_H_ */
> diff --git a/drivers/gpu/drm/sprd/sprd_gem.c b/drivers/gpu/drm/sprd/sprd_gem.c
> new file mode 100644
> index 0000000..c617c8b
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_gem.c
> @@ -0,0 +1,178 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#include <linux/dma-buf.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <drm/drm_prime.h>
> +
> +#include "sprd_drm.h"
> +#include "sprd_gem.h"
> +
> +static struct sprd_gem_obj *sprd_gem_obj_create(struct drm_device *drm,
> + unsigned long size)
> +{
> + struct sprd_gem_obj *sprd_gem;
> + int ret;
> +
> + sprd_gem = kzalloc(sizeof(*sprd_gem), GFP_KERNEL);
> + if (!sprd_gem)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = drm_gem_object_init(drm, &sprd_gem->base, size);
> + if (ret < 0) {
> + DRM_ERROR("failed to initialize gem object\n");
> + goto error;
> + }
> +
> + ret = drm_gem_create_mmap_offset(&sprd_gem->base);
> + if (ret) {
> + drm_gem_object_release(&sprd_gem->base);
> + goto error;
> + }
> +
> + return sprd_gem;
> +
> +error:
> + kfree(sprd_gem);
> + return ERR_PTR(ret);
> +}
> +
> +void sprd_gem_free_object(struct drm_gem_object *obj)
> +{
> + struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> +
> + DRM_DEBUG("gem = %p\n", obj);
> +
> + if (sprd_gem->vaddr)
> + dma_alloc_wc(obj->dev->dev, obj->size,

dma_free_wc

Best regards
Thomas

[1]
https://cgit.freedesktop.org/drm/drm-tip/tree/drivers/gpu/drm/drm_gem_cma_helper.c


> + sprd_gem->vaddr, sprd_gem->dma_addr);
> + else if (sprd_gem->sgtb)
> + drm_prime_gem_destroy(obj, sprd_gem->sgtb);
> +
> + drm_gem_object_release(obj);
> +
> + kfree(sprd_gem);
> +}
> +
> +int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm,
> + struct drm_mode_create_dumb *args)
> +{
> + struct sprd_gem_obj *sprd_gem;
> + int ret;
> +
> + args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> + args->size = round_up(args->pitch * args->height, PAGE_SIZE);
> +
> + sprd_gem = sprd_gem_obj_create(drm, args->size);
> + if (IS_ERR(sprd_gem))
> + return PTR_ERR(sprd_gem);
> +
> + sprd_gem->vaddr = dma_alloc_wc(drm->dev, args->size,
> + &sprd_gem->dma_addr, GFP_KERNEL | __GFP_NOWARN | GFP_DMA);
> + if (!sprd_gem->vaddr) {
> + DRM_ERROR("failed to allocate buffer with size %llu\n",
> + args->size);
> + ret = -ENOMEM;
> + goto error;
> + }
> +
> + ret = drm_gem_handle_create(file_priv, &sprd_gem->base, &args->handle);
> + if (ret)
> + goto error;
> +
> + drm_gem_object_put_unlocked(&sprd_gem->base);
> +
> + return 0;
> +
> +error:
> + sprd_gem_free_object(&sprd_gem->base);
> + return ret;
> +}
> +
> +static int sprd_gem_cma_object_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma)
> +
> +{
> + int ret;
> + struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> +
> + vma->vm_flags &= ~VM_PFNMAP;
> + vma->vm_pgoff = 0;
> +
> + ret = dma_mmap_wc(obj->dev->dev, vma,
> + sprd_gem->vaddr, sprd_gem->dma_addr,
> + vma->vm_end - vma->vm_start);
> + if (ret)
> + drm_gem_vm_close(vma);
> +
> + return ret;
> +}
> +
> +int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + struct drm_gem_object *obj;
> + int ret;
> +
> + ret = drm_gem_mmap(filp, vma);
> + if (ret)
> + return ret;
> +
> + obj = vma->vm_private_data;
> +
> + return sprd_gem_cma_object_mmap(obj, vma);
> +}
> +
> +int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma)
> +{
> + int ret;
> +
> + ret = drm_gem_mmap_obj(obj, obj->size, vma);
> + if (ret)
> + return ret;
> +
> + return sprd_gem_cma_object_mmap(obj, vma);
> +}
> +
> +struct sg_table *sprd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj)
> +{
> + struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> + struct sg_table *sgtb;
> + int ret;
> +
> + sgtb = kzalloc(sizeof(*sgtb), GFP_KERNEL);
> + if (!sgtb)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = dma_get_sgtable(obj->dev->dev, sgtb, sprd_gem->vaddr,
> + sprd_gem->dma_addr, obj->size);
> + if (ret) {
> + DRM_ERROR("failed to allocate sg_table, %d\n", ret);
> + kfree(sgtb);
> + return ERR_PTR(ret);
> + }
> +
> + return sgtb;
> +}
> +
> +struct drm_gem_object *sprd_gem_prime_import_sg_table(struct drm_device *drm,
> + struct dma_buf_attachment *attach, struct sg_table *sgtb)
> +{
> + struct sprd_gem_obj *sprd_gem;
> +
> + sprd_gem = sprd_gem_obj_create(drm, attach->dmabuf->size);
> + if (IS_ERR(sprd_gem))
> + return ERR_CAST(sprd_gem);
> +
> + DRM_DEBUG("gem = %p\n", &sprd_gem->base);
> +
> + if (sgtb->nents == 1)
> + sprd_gem->dma_addr = sg_dma_address(sgtb->sgl);
> +
> + sprd_gem->sgtb = sgtb;
> +
> + return &sprd_gem->base;
> +}
> diff --git a/drivers/gpu/drm/sprd/sprd_gem.h b/drivers/gpu/drm/sprd/sprd_gem.h
> new file mode 100644
> index 0000000..4c10d8a
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_gem.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef _SPRD_GEM_H_
> +#define _SPRD_GEM_H_
> +
> +#include <drm/drm_gem.h>
> +
> +struct sprd_gem_obj {
> + struct drm_gem_object base;
> + dma_addr_t dma_addr;
> + struct sg_table *sgtb;
> + void *vaddr;
> +};
> +
> +#define to_sprd_gem_obj(x) container_of(x, struct sprd_gem_obj, base)
> +
> +void sprd_gem_free_object(struct drm_gem_object *gem);
> +int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
> + struct drm_mode_create_dumb *args);
> +int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma);
> +int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma);
> +struct sg_table *sprd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj);
> +struct drm_gem_object *sprd_gem_prime_import_sg_table(struct drm_device *dev,
> + struct dma_buf_attachment *attach, struct sg_table *sgtb);
> +
> +#endif
>

--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Maxfeldstr. 5, 90409 Nürnberg, Germany
(HRB 36809, AG Nürnberg)
Geschäftsführer: Felix Imendörffer


Attachments:
signature.asc (499.00 B)
OpenPGP digital signature

2019-12-10 10:41:49

by Daniel Vetter

[permalink] [raw]
Subject: Re: [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master

On Tue, Dec 10, 2019 at 04:36:29PM +0800, Kevin Tang wrote:
> From: Kevin Tang <[email protected]>
>
> Adds drm support for the Unisoc's display subsystem.
>
> This is drm device and gem driver. This driver provides support for the
> Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/sprd/Kconfig | 14 ++
> drivers/gpu/drm/sprd/Makefile | 8 ++
> drivers/gpu/drm/sprd/sprd_drm.c | 287 ++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_drm.h | 19 +++
> drivers/gpu/drm/sprd/sprd_gem.c | 178 +++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_gem.h | 30 +++++
> 8 files changed, 539 insertions(+)
> create mode 100644 drivers/gpu/drm/sprd/Kconfig
> create mode 100644 drivers/gpu/drm/sprd/Makefile
> create mode 100644 drivers/gpu/drm/sprd/sprd_drm.c
> create mode 100644 drivers/gpu/drm/sprd/sprd_drm.h
> create mode 100644 drivers/gpu/drm/sprd/sprd_gem.c
> create mode 100644 drivers/gpu/drm/sprd/sprd_gem.h
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index bfdadc3..cead12c 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -387,6 +387,8 @@ source "drivers/gpu/drm/aspeed/Kconfig"
>
> source "drivers/gpu/drm/mcde/Kconfig"
>
> +source "drivers/gpu/drm/sprd/Kconfig"
> +
> # Keep legacy drivers last
>
> menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 9f1c7c4..85ca211 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -122,3 +122,4 @@ obj-$(CONFIG_DRM_LIMA) += lima/
> obj-$(CONFIG_DRM_PANFROST) += panfrost/
> obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
> obj-$(CONFIG_DRM_MCDE) += mcde/
> +obj-$(CONFIG_DRM_SPRD) += sprd/
> diff --git a/drivers/gpu/drm/sprd/Kconfig b/drivers/gpu/drm/sprd/Kconfig
> new file mode 100644
> index 0000000..79f286b
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/Kconfig
> @@ -0,0 +1,14 @@
> +config DRM_SPRD
> + tristate "DRM Support for Unisoc SoCs Platform"
> + depends on ARCH_SPRD
> + depends on DRM && OF
> + select DRM_KMS_HELPER
> + select DRM_GEM_CMA_HELPER
> + select DRM_KMS_CMA_HELPER
> + select DRM_MIPI_DSI
> + select DRM_PANEL
> + select VIDEOMODE_HELPERS
> + select BACKLIGHT_CLASS_DEVICE
> + help
> + Choose this option if you have a Unisoc chipsets.
> + If M is selected the module will be called sprd-drm.
> \ No newline at end of file
> diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
> new file mode 100644
> index 0000000..df0b316
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +ccflags-y += -Iinclude/drm
> +
> +subdir-ccflags-y += -I$(src)
> +
> +obj-y := sprd_drm.o \
> + sprd_gem.o
> \ No newline at end of file
> diff --git a/drivers/gpu/drm/sprd/sprd_drm.c b/drivers/gpu/drm/sprd/sprd_drm.c
> new file mode 100644
> index 0000000..ec16fee
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_drm.c
> @@ -0,0 +1,287 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#include <linux/component.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "sprd_drm.h"
> +#include "sprd_gem.h"
> +
> +#define DRIVER_NAME "sprd"
> +#define DRIVER_DESC "Spreadtrum SoCs' DRM Driver"
> +#define DRIVER_DATE "20180501"
> +#define DRIVER_MAJOR 1
> +#define DRIVER_MINOR 0
> +
> +static const struct drm_mode_config_helper_funcs sprd_drm_mode_config_helper = {
> + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
> +};
> +
> +static const struct drm_mode_config_funcs sprd_drm_mode_config_funcs = {
> + .fb_create = drm_gem_fb_create,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static void sprd_drm_mode_config_init(struct drm_device *drm)
> +{
> + drm_mode_config_init(drm);
> +
> + drm->mode_config.min_width = 0;
> + drm->mode_config.min_height = 0;
> + drm->mode_config.max_width = 8192;
> + drm->mode_config.max_height = 8192;
> + drm->mode_config.allow_fb_modifiers = true;
> +
> + drm->mode_config.funcs = &sprd_drm_mode_config_funcs;
> + drm->mode_config.helper_private = &sprd_drm_mode_config_helper;
> +}
> +
> +static const struct file_operations sprd_drm_fops = {
> + .owner = THIS_MODULE,
> + .open = drm_open,
> + .release = drm_release,
> + .unlocked_ioctl = drm_ioctl,
> + .compat_ioctl = drm_compat_ioctl,
> + .poll = drm_poll,
> + .read = drm_read,
> + .llseek = no_llseek,
> + .mmap = sprd_gem_cma_mmap,
> +};
> +
> +static struct drm_driver sprd_drm_drv = {
> + .driver_features = DRIVER_GEM | DRIVER_MODESET |
> + DRIVER_ATOMIC | DRIVER_HAVE_IRQ,

DRIVER_HAVE_IRQ only does something for pci drivers, please remove this.

Just a quick drive-by, I didn't read through your driver really.
-Daniel

> + .fops = &sprd_drm_fops,
> +
> + .gem_vm_ops = &drm_gem_cma_vm_ops,
> + .gem_free_object_unlocked = sprd_gem_free_object,
> + .dumb_create = sprd_gem_cma_dumb_create,
> +
> + .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> + .gem_prime_import = drm_gem_prime_import,
> + .gem_prime_import_sg_table = sprd_gem_prime_import_sg_table,
> +
> + .name = DRIVER_NAME,
> + .desc = DRIVER_DESC,
> + .date = DRIVER_DATE,
> + .major = DRIVER_MAJOR,
> + .minor = DRIVER_MINOR,
> +};
> +
> +static int sprd_drm_bind(struct device *dev)
> +{
> + struct drm_device *drm;
> + struct sprd_drm *sprd;
> + int err;
> +
> + drm = drm_dev_alloc(&sprd_drm_drv, dev);
> + if (IS_ERR(drm))
> + return PTR_ERR(drm);
> +
> + dev_set_drvdata(dev, drm);
> +
> + sprd = devm_kzalloc(drm->dev, sizeof(*sprd), GFP_KERNEL);
> + if (!sprd) {
> + err = -ENOMEM;
> + goto err_free_drm;
> + }
> + drm->dev_private = sprd;
> +
> + sprd_drm_mode_config_init(drm);
> +
> + /* bind and init sub drivers */
> + err = component_bind_all(drm->dev, drm);
> + if (err) {
> + DRM_ERROR("failed to bind all component.\n");
> + goto err_dc_cleanup;
> + }
> +
> + /* vblank init */
> + err = drm_vblank_init(drm, drm->mode_config.num_crtc);
> + if (err) {
> + DRM_ERROR("failed to initialize vblank.\n");
> + goto err_unbind_all;
> + }
> + /* with irq_enabled = true, we can use the vblank feature. */
> + drm->irq_enabled = true;
> +
> + /* reset all the states of crtc/plane/encoder/connector */
> + drm_mode_config_reset(drm);
> +
> + /* init kms poll for handling hpd */
> + drm_kms_helper_poll_init(drm);
> +
> + err = drm_dev_register(drm, 0);
> + if (err < 0)
> + goto err_kms_helper_poll_fini;
> +
> + return 0;
> +
> +err_kms_helper_poll_fini:
> + drm_kms_helper_poll_fini(drm);
> +err_unbind_all:
> + component_unbind_all(drm->dev, drm);
> +err_dc_cleanup:
> + drm_mode_config_cleanup(drm);
> +err_free_drm:
> + drm_dev_put(drm);
> + return err;
> +}
> +
> +static void sprd_drm_unbind(struct device *dev)
> +{
> + drm_put_dev(dev_get_drvdata(dev));
> +}
> +
> +static const struct component_master_ops sprd_drm_component_ops = {
> + .bind = sprd_drm_bind,
> + .unbind = sprd_drm_unbind,
> +};
> +
> +static int compare_of(struct device *dev, void *data)
> +{
> + struct device_node *np = data;
> +
> + DRM_DEBUG("compare %s\n", np->full_name);
> +
> + return dev->of_node == np;
> +}
> +
> +static int sprd_drm_component_probe(struct device *dev,
> + const struct component_master_ops *m_ops)
> +{
> + struct device_node *ep, *port, *remote;
> + struct component_match *match = NULL;
> + int i;
> +
> + if (!dev->of_node)
> + return -EINVAL;
> +
> + /*
> + * Bind the crtc's ports first, so that drm_of_find_possible_crtcs()
> + * called from encoder's .bind callbacks works as expected
> + */
> + for (i = 0; ; i++) {
> + port = of_parse_phandle(dev->of_node, "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;
> + }
> +
> + if (!match) {
> + dev_err(dev, "no available port\n");
> + return -ENODEV;
> + }
> +
> + /*
> + * For bound crtcs, bind the encoders attached to their remote endpoint
> + */
> + for (i = 0; ; i++) {
> + port = of_parse_phandle(dev->of_node, "ports", i);
> + if (!port)
> + break;
> +
> + if (!of_device_is_available(port->parent)) {
> + of_node_put(port);
> + continue;
> + }
> +
> + 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);
> + }
> + of_node_put(port);
> + }
> +
> + return component_master_add_with_match(dev, m_ops, match);
> +}
> +
> +static int sprd_drm_probe(struct platform_device *pdev)
> +{
> + int ret;
> +
> + ret = dma_set_mask_and_coherent(&pdev->dev, ~0);
> + if (ret)
> + DRM_ERROR("dma_set_mask_and_coherent failed (%d)\n", ret);
> +
> + return sprd_drm_component_probe(&pdev->dev, &sprd_drm_component_ops);
> +}
> +
> +static int sprd_drm_remove(struct platform_device *pdev)
> +{
> + component_master_del(&pdev->dev, &sprd_drm_component_ops);
> + return 0;
> +}
> +
> +static void sprd_drm_shutdown(struct platform_device *pdev)
> +{
> + struct drm_device *drm = platform_get_drvdata(pdev);
> +
> + if (!drm) {
> + DRM_WARN("drm device is not available, no shutdown\n");
> + return;
> + }
> +
> + drm_atomic_helper_shutdown(drm);
> +}
> +
> +static const struct of_device_id sprd_drm_match_table[] = {
> + { .compatible = "sprd,display-subsystem",},
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, sprd_drm_match_table);
> +
> +static struct platform_driver sprd_drm_driver = {
> + .probe = sprd_drm_probe,
> + .remove = sprd_drm_remove,
> + .shutdown = sprd_drm_shutdown,
> + .driver = {
> + .name = "sprd-drm-drv",
> + .of_match_table = sprd_drm_match_table,
> + },
> +};
> +
> +module_platform_driver(sprd_drm_driver);
> +
> +MODULE_AUTHOR("Leon He <[email protected]>");
> +MODULE_AUTHOR("Kevin Tang <[email protected]>");
> +MODULE_DESCRIPTION("Unisoc DRM KMS Master Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/sprd/sprd_drm.h b/drivers/gpu/drm/sprd/sprd_drm.h
> new file mode 100644
> index 0000000..e840e65
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef _SPRD_DRM_H_
> +#define _SPRD_DRM_H_
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_print.h>
> +
> +struct sprd_drm {
> + struct drm_device *drm;
> + struct drm_atomic_state *state;
> + struct device *dpu_dev;
> + struct device *gsp_dev;
> +};
> +
> +#endif /* _SPRD_DRM_H_ */
> diff --git a/drivers/gpu/drm/sprd/sprd_gem.c b/drivers/gpu/drm/sprd/sprd_gem.c
> new file mode 100644
> index 0000000..c617c8b
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_gem.c
> @@ -0,0 +1,178 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#include <linux/dma-buf.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <drm/drm_prime.h>
> +
> +#include "sprd_drm.h"
> +#include "sprd_gem.h"
> +
> +static struct sprd_gem_obj *sprd_gem_obj_create(struct drm_device *drm,
> + unsigned long size)
> +{
> + struct sprd_gem_obj *sprd_gem;
> + int ret;
> +
> + sprd_gem = kzalloc(sizeof(*sprd_gem), GFP_KERNEL);
> + if (!sprd_gem)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = drm_gem_object_init(drm, &sprd_gem->base, size);
> + if (ret < 0) {
> + DRM_ERROR("failed to initialize gem object\n");
> + goto error;
> + }
> +
> + ret = drm_gem_create_mmap_offset(&sprd_gem->base);
> + if (ret) {
> + drm_gem_object_release(&sprd_gem->base);
> + goto error;
> + }
> +
> + return sprd_gem;
> +
> +error:
> + kfree(sprd_gem);
> + return ERR_PTR(ret);
> +}
> +
> +void sprd_gem_free_object(struct drm_gem_object *obj)
> +{
> + struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> +
> + DRM_DEBUG("gem = %p\n", obj);
> +
> + if (sprd_gem->vaddr)
> + dma_alloc_wc(obj->dev->dev, obj->size,
> + sprd_gem->vaddr, sprd_gem->dma_addr);
> + else if (sprd_gem->sgtb)
> + drm_prime_gem_destroy(obj, sprd_gem->sgtb);
> +
> + drm_gem_object_release(obj);
> +
> + kfree(sprd_gem);
> +}
> +
> +int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm,
> + struct drm_mode_create_dumb *args)
> +{
> + struct sprd_gem_obj *sprd_gem;
> + int ret;
> +
> + args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> + args->size = round_up(args->pitch * args->height, PAGE_SIZE);
> +
> + sprd_gem = sprd_gem_obj_create(drm, args->size);
> + if (IS_ERR(sprd_gem))
> + return PTR_ERR(sprd_gem);
> +
> + sprd_gem->vaddr = dma_alloc_wc(drm->dev, args->size,
> + &sprd_gem->dma_addr, GFP_KERNEL | __GFP_NOWARN | GFP_DMA);
> + if (!sprd_gem->vaddr) {
> + DRM_ERROR("failed to allocate buffer with size %llu\n",
> + args->size);
> + ret = -ENOMEM;
> + goto error;
> + }
> +
> + ret = drm_gem_handle_create(file_priv, &sprd_gem->base, &args->handle);
> + if (ret)
> + goto error;
> +
> + drm_gem_object_put_unlocked(&sprd_gem->base);
> +
> + return 0;
> +
> +error:
> + sprd_gem_free_object(&sprd_gem->base);
> + return ret;
> +}
> +
> +static int sprd_gem_cma_object_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma)
> +
> +{
> + int ret;
> + struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> +
> + vma->vm_flags &= ~VM_PFNMAP;
> + vma->vm_pgoff = 0;
> +
> + ret = dma_mmap_wc(obj->dev->dev, vma,
> + sprd_gem->vaddr, sprd_gem->dma_addr,
> + vma->vm_end - vma->vm_start);
> + if (ret)
> + drm_gem_vm_close(vma);
> +
> + return ret;
> +}
> +
> +int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + struct drm_gem_object *obj;
> + int ret;
> +
> + ret = drm_gem_mmap(filp, vma);
> + if (ret)
> + return ret;
> +
> + obj = vma->vm_private_data;
> +
> + return sprd_gem_cma_object_mmap(obj, vma);
> +}
> +
> +int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma)
> +{
> + int ret;
> +
> + ret = drm_gem_mmap_obj(obj, obj->size, vma);
> + if (ret)
> + return ret;
> +
> + return sprd_gem_cma_object_mmap(obj, vma);
> +}
> +
> +struct sg_table *sprd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj)
> +{
> + struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> + struct sg_table *sgtb;
> + int ret;
> +
> + sgtb = kzalloc(sizeof(*sgtb), GFP_KERNEL);
> + if (!sgtb)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = dma_get_sgtable(obj->dev->dev, sgtb, sprd_gem->vaddr,
> + sprd_gem->dma_addr, obj->size);
> + if (ret) {
> + DRM_ERROR("failed to allocate sg_table, %d\n", ret);
> + kfree(sgtb);
> + return ERR_PTR(ret);
> + }
> +
> + return sgtb;
> +}
> +
> +struct drm_gem_object *sprd_gem_prime_import_sg_table(struct drm_device *drm,
> + struct dma_buf_attachment *attach, struct sg_table *sgtb)
> +{
> + struct sprd_gem_obj *sprd_gem;
> +
> + sprd_gem = sprd_gem_obj_create(drm, attach->dmabuf->size);
> + if (IS_ERR(sprd_gem))
> + return ERR_CAST(sprd_gem);
> +
> + DRM_DEBUG("gem = %p\n", &sprd_gem->base);
> +
> + if (sgtb->nents == 1)
> + sprd_gem->dma_addr = sg_dma_address(sgtb->sgl);
> +
> + sprd_gem->sgtb = sgtb;
> +
> + return &sprd_gem->base;
> +}
> diff --git a/drivers/gpu/drm/sprd/sprd_gem.h b/drivers/gpu/drm/sprd/sprd_gem.h
> new file mode 100644
> index 0000000..4c10d8a
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_gem.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef _SPRD_GEM_H_
> +#define _SPRD_GEM_H_
> +
> +#include <drm/drm_gem.h>
> +
> +struct sprd_gem_obj {
> + struct drm_gem_object base;
> + dma_addr_t dma_addr;
> + struct sg_table *sgtb;
> + void *vaddr;
> +};
> +
> +#define to_sprd_gem_obj(x) container_of(x, struct sprd_gem_obj, base)
> +
> +void sprd_gem_free_object(struct drm_gem_object *gem);
> +int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
> + struct drm_mode_create_dumb *args);
> +int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma);
> +int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
> + struct vm_area_struct *vma);
> +struct sg_table *sprd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj);
> +struct drm_gem_object *sprd_gem_prime_import_sg_table(struct drm_device *dev,
> + struct dma_buf_attachment *attach, struct sg_table *sgtb);
> +
> +#endif
> --
> 2.7.4
>

--
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

2019-12-10 10:47:03

by Thomas Zimmermann

[permalink] [raw]
Subject: Re: [PATCH RFC 4/8] drm/sprd: add Unisoc's drm display controller driver

Hi

Am 10.12.19 um 09:36 schrieb Kevin Tang:
> From: Kevin Tang <[email protected]>
>
> Adds DPU(Display Processor Unit) support for the Unisoc's display subsystem.
> It's support multi planes, scaler, rotation, PQ(Picture Quality) and more.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> drivers/gpu/drm/sprd/Makefile | 6 +-
> drivers/gpu/drm/sprd/disp_lib.c | 290 +++++++
> drivers/gpu/drm/sprd/disp_lib.h | 40 +
> drivers/gpu/drm/sprd/dpu/Makefile | 8 +
> drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 1464 +++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_dpu.c | 1152 +++++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_dpu.h | 217 ++++++
> 7 files changed, 3176 insertions(+), 1 deletion(-)
> create mode 100644 drivers/gpu/drm/sprd/disp_lib.c
> create mode 100644 drivers/gpu/drm/sprd/disp_lib.h
> create mode 100644 drivers/gpu/drm/sprd/dpu/Makefile
> create mode 100644 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.c
> create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.h
>
> diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
> index df0b316..3f188ab 100644
> --- a/drivers/gpu/drm/sprd/Makefile
> +++ b/drivers/gpu/drm/sprd/Makefile
> @@ -5,4 +5,8 @@ ccflags-y += -Iinclude/drm
> subdir-ccflags-y += -I$(src)
>
> obj-y := sprd_drm.o \
> - sprd_gem.o
> \ No newline at end of file
> + sprd_gem.o \
> + sprd_dpu.o
> +
> +obj-y += disp_lib.o
> +obj-y += dpu/
> \ No newline at end of file
> diff --git a/drivers/gpu/drm/sprd/disp_lib.c b/drivers/gpu/drm/sprd/disp_lib.c
> new file mode 100644
> index 0000000..cadd1ad
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/disp_lib.c
> @@ -0,0 +1,290 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#define pr_fmt(__fmt) "[drm][%20s] "__fmt, __func__
> +
> +#include <linux/device.h>
> +#include <linux/libfdt.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/fs.h>
> +
> +#include "disp_lib.h"
> +
> +struct bmp_header {
> + u16 magic;
> + u32 size;
> + u32 unused;
> + u32 start;
> +} __attribute__((__packed__));
> +
> +struct dib_header {
> + u32 size;
> + u32 width;
> + u32 height;
> + u16 planes;
> + u16 bpp;
> + u32 compression;
> + u32 data_size;
> + u32 h_res;
> + u32 v_res;
> + u32 colours;
> + u32 important_colours;
> + u32 red_mask;
> + u32 green_mask;
> + u32 blue_mask;
> + u32 alpha_mask;
> + u32 colour_space;
> + u32 unused[12];
> +} __attribute__((__packed__));
> +
> +int str_to_u32_array(const char *p, u32 base, u32 array[])
> +{
> + const char *start = p;
> + char str[12];
> + int length = 0;
> + int i, ret;
> +
> + pr_info("input: %s", p);
> +
> + for (i = 0 ; i < 255; i++) {
> + while (*p == ' ')
> + p++;
> + if (*p == '\0')
> + break;
> + start = p;
> +
> + while ((*p != ' ') && (*p != '\0'))
> + p++;
> +
> + if ((p - start) >= sizeof(str))
> + break;
> +
> + memset(str, 0, sizeof(str));
> + memcpy(str, start, p - start);
> +
> + ret = kstrtou32(str, base, &array[i]);
> + if (ret) {
> + DRM_ERROR("input format error\n");
> + break;
> + }
> +
> + length++;
> + }
> +
> + return length;
> +}
> +EXPORT_SYMBOL_GPL(str_to_u32_array);
> +
> +int load_dtb_to_mem(const char *name, void **blob)
> +{
> + ssize_t ret;
> + u32 count;
> + struct fdt_header dtbhead;
> + loff_t pos = 0;
> + struct file *fdtb;
> +
> +
> + fdtb = filp_open(name, O_RDONLY, 0644);
> + if (IS_ERR(fdtb)) {
> + DRM_ERROR("%s open file error\n", __func__);
> + return PTR_ERR(fdtb);
> + }
> +
> + ret = kernel_read(fdtb, &dtbhead, sizeof(dtbhead), &pos);
> + pos = 0;
> + count = ntohl(dtbhead.totalsize);
> + *blob = kzalloc(count, GFP_KERNEL);
> + if (*blob == NULL) {
> + filp_close(fdtb, NULL);
> + return -ENOMEM;
> + }
> + ret = kernel_read(fdtb, *blob, count, &pos);
> +
> + if (ret != count) {
> + DRM_ERROR("Read to mem fail: ret %zd size%x\n", ret, count);
> + kfree(*blob);
> + *blob = NULL;
> + filp_close(fdtb, NULL);
> + return ret < 0 ? ret : -ENODEV;
> + }
> +
> + filp_close(fdtb, NULL);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(load_dtb_to_mem);
> +
> +int str_to_u8_array(const char *p, u32 base, u8 array[])
> +{
> + const char *start = p;
> + char str[12];
> + int length = 0;
> + int i, ret;
> +
> + pr_info("input: %s", p);
> +
> + for (i = 0 ; i < 255; i++) {
> + while (*p == ' ')
> + p++;
> + if (*p == '\0')
> + break;
> + start = p;
> +
> + while ((*p != ' ') && (*p != '\0'))
> + p++;
> +
> + if ((p - start) >= sizeof(str))
> + break;
> +
> + memset(str, 0, sizeof(str));
> + memcpy(str, start, p - start);
> +
> + ret = kstrtou8(str, base, &array[i]);
> + if (ret) {
> + DRM_ERROR("input format error\n");
> + break;
> + }
> +
> + length++;
> + }
> +
> + return length;
> +}
> +EXPORT_SYMBOL_GPL(str_to_u8_array);
> +
> +int dump_bmp32(const char *p, u32 width, u32 height,
> + bool noflip, const char *filename)
> +{
> + struct file *fp;
> + mm_segment_t fs;
> + loff_t pos;
> + struct dib_header dib_header = {
> + .size = sizeof(dib_header),
> + .width = width,
> + .height = noflip ? -height : height,
> + .planes = 1,
> + .bpp = 32,
> + .compression = 3,
> + .data_size = 4 * width * height,
> + .h_res = 0xB13,
> + .v_res = 0xB13,
> + .colours = 0,
> + .important_colours = 0,
> + .red_mask = 0x000000FF,
> + .green_mask = 0x0000FF00,
> + .blue_mask = 0x00FF0000,
> + .alpha_mask = 0xFF000000,
> + .colour_space = 0x57696E20,
> + };
> + struct bmp_header bmp_header = {
> + .magic = 0x4d42,
> + .size = (width * height * 4) +
> + sizeof(bmp_header) + sizeof(dib_header),
> + .start = sizeof(bmp_header) + sizeof(dib_header),
> + };
> +
> + fp = filp_open(filename, O_RDWR | O_CREAT, 0644);
> + if (IS_ERR(fp)) {
> + DRM_ERROR("failed to open %s: %ld\n", filename, PTR_ERR(fp));
> + return PTR_ERR(fp);
> + }
> +
> + fs = get_fs();
> + set_fs(KERNEL_DS);
> + pos = 0;
> +
> + vfs_write(fp, (const char *)&bmp_header, sizeof(bmp_header), &pos);
> + vfs_write(fp, (const char *)&dib_header, sizeof(dib_header), &pos);
> + vfs_write(fp, p, width * height * 4, &pos);
> +
> + filp_close(fp, NULL);
> + set_fs(fs);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(dump_bmp32);
> +
> +void *disp_ops_attach(const char *str, struct list_head *head)
> +{
> + struct ops_list *list;
> + const char *ver;
> +
> + list_for_each_entry(list, head, head) {
> + ver = list->entry->ver;
> + if (!strcmp(str, ver))
> + return list->entry->ops;
> + }
> +
> + DRM_ERROR("attach disp ops %s failed\n", str);
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(disp_ops_attach);
> +
> +int disp_ops_register(struct ops_entry *entry, struct list_head *head)
> +{
> + struct ops_list *list;
> +
> + list = kzalloc(sizeof(struct ops_list), GFP_KERNEL);
> + if (!list)
> + return -ENOMEM;
> +
> + list->entry = entry;
> + list_add(&list->head, head);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(disp_ops_register);
> +
> +struct device *sprd_disp_pipe_get_by_port(struct device *dev, int port)
> +{
> + struct device_node *np = dev->of_node;
> + struct device_node *endpoint;
> + struct device_node *remote_node;
> + struct platform_device *remote_pdev;
> +
> + endpoint = of_graph_get_endpoint_by_regs(np, port, 0);
> + if (!endpoint) {
> + DRM_ERROR("%s/port%d/endpoint0 was not found\n",
> + np->full_name, port);
> + return NULL;
> + }
> +
> + remote_node = of_graph_get_remote_port_parent(endpoint);
> + if (!remote_node) {
> + DRM_ERROR("device node was not found by endpoint0\n");
> + return NULL;
> + }
> +
> + remote_pdev = of_find_device_by_node(remote_node);
> + if (remote_pdev == NULL) {
> + DRM_ERROR("find %s platform device failed\n",
> + remote_node->full_name);
> + return NULL;
> + }
> +
> + return &remote_pdev->dev;
> +}
> +EXPORT_SYMBOL_GPL(sprd_disp_pipe_get_by_port);
> +
> +struct device *sprd_disp_pipe_get_input(struct device *dev)
> +{
> + return sprd_disp_pipe_get_by_port(dev, 1);
> +}
> +EXPORT_SYMBOL_GPL(sprd_disp_pipe_get_input);
> +
> +struct device *sprd_disp_pipe_get_output(struct device *dev)
> +{
> + return sprd_disp_pipe_get_by_port(dev, 0);
> +}
> +EXPORT_SYMBOL_GPL(sprd_disp_pipe_get_output);
> +
> +MODULE_AUTHOR("Leon He <[email protected]>");
> +MODULE_AUTHOR("Kevin Tang <[email protected]>");
> +MODULE_DESCRIPTION("Unisoc display common API library");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/sprd/disp_lib.h b/drivers/gpu/drm/sprd/disp_lib.h
> new file mode 100644
> index 0000000..7900b89
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/disp_lib.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef _DISP_LIB_H_
> +#define _DISP_LIB_H_
> +
> +#include <linux/list.h>
> +#include <drm/drm_print.h>
> +
> +#ifdef pr_fmt
> +#undef pr_fmt
> +#define pr_fmt(__fmt) "[drm][%20s] "__fmt, __func__
> +#endif
> +
> +struct ops_entry {
> + const char *ver;
> + void *ops;
> +};
> +
> +struct ops_list {
> + struct list_head head;
> + struct ops_entry *entry;
> +};
> +
> +int str_to_u32_array(const char *p, u32 base, u32 array[]);
> +int str_to_u8_array(const char *p, u32 base, u8 array[]);
> +int dump_bmp32(const char *p, u32 width, u32 height,
> + bool bgra, const char *filename);
> +int load_dtb_to_mem(const char *name, void **blob);

The bitmap-loading code seems out of place in a driver. It should be a
helper. And efifb has similar code already. Is there an opportunity to
share the implementation?

I cannot find any caller of load_dtb_to_mem(). Where is it being used?

Best regards
Thomas

> +
> +void *disp_ops_attach(const char *str, struct list_head *head);
> +int disp_ops_register(struct ops_entry *entry, struct list_head *head);
> +
> +struct device *sprd_disp_pipe_get_by_port(struct device *dev, int port);
> +struct device *sprd_disp_pipe_get_input(struct device *dev);
> +struct device *sprd_disp_pipe_get_output(struct device *dev);
> +
> +#endif
> diff --git a/drivers/gpu/drm/sprd/dpu/Makefile b/drivers/gpu/drm/sprd/dpu/Makefile
> new file mode 100644
> index 0000000..d960107
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +ifdef CONFIG_ARM64
> +KBUILD_CFLAGS += -mstrict-align
> +endif
> +
> +obj-y += dpu_r2p0.o
> +
> diff --git a/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> new file mode 100644
> index 0000000..4c0a539
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> @@ -0,0 +1,1464 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/wait.h>
> +#include <linux/workqueue.h>
> +#include "sprd_dpu.h"
> +
> +#define DISPC_INT_FBC_PLD_ERR_MASK BIT(8)
> +#define DISPC_INT_FBC_HDR_ERR_MASK BIT(9)
> +
> +#define DISPC_INT_MMU_INV_WR_MASK BIT(19)
> +#define DISPC_INT_MMU_INV_RD_MASK BIT(18)
> +#define DISPC_INT_MMU_VAOR_WR_MASK BIT(17)
> +#define DISPC_INT_MMU_VAOR_RD_MASK BIT(16)
> +
> +#define XFBC8888_HEADER_SIZE(w, h) (ALIGN((w) * (h) / (8 * 8) / 2, 128))
> +#define XFBC8888_PAYLOAD_SIZE(w, h) (w * h * 4)
> +#define XFBC8888_BUFFER_SIZE(w, h) (XFBC8888_HEADER_SIZE(w, h) \
> + + XFBC8888_PAYLOAD_SIZE(w, h))
> +
> +#define XFBC565_HEADER_SIZE(w, h) (ALIGN((w) * (h) / (16 * 8) / 2, 128))
> +#define XFBC565_PAYLOAD_SIZE(w, h) (w * h * 2)
> +#define XFBC565_BUFFER_SIZE(w, h) (XFBC565_HEADER_SIZE(w, h) \
> + + XFBC565_PAYLOAD_SIZE(w, h))
> +
> +#define SLP_BRIGHTNESS_THRESHOLD 0x20
> +
> +struct layer_reg {
> + u32 addr[4];
> + u32 ctrl;
> + u32 size;
> + u32 pitch;
> + u32 pos;
> + u32 alpha;
> + u32 ck;
> + u32 pallete;
> + u32 crop_start;
> +};
> +
> +struct wb_region_reg {
> + u32 pos;
> + u32 size;
> +};
> +
> +struct dpu_reg {
> + u32 dpu_version;
> + u32 dpu_ctrl;
> + u32 dpu_cfg0;
> + u32 dpu_cfg1;
> + u32 dpu_cfg2;
> + u32 dpu_secure;
> + u32 reserved_0x0018_0x001C[2];
> + u32 panel_size;
> + u32 blend_size;
> + u32 reserved_0x0028;
> + u32 bg_color;
> + struct layer_reg layers[8];
> + u32 wb_base_addr;
> + u32 wb_ctrl;
> + u32 wb_cfg;
> + u32 wb_pitch;
> + struct wb_region_reg region[3];
> + u32 reserved_0x01D8_0x01DC[2];
> + u32 dpu_int_en;
> + u32 dpu_int_clr;
> + u32 dpu_int_sts;
> + u32 dpu_int_raw;
> + u32 dpi_ctrl;
> + u32 dpi_h_timing;
> + u32 dpi_v_timing;
> + u32 reserved_0x01FC;
> + u32 dpu_enhance_cfg;
> + u32 reserved_0x0204_0x020C[3];
> + u32 epf_epsilon;
> + u32 epf_gain0_3;
> + u32 epf_gain4_7;
> + u32 epf_diff;
> + u32 reserved_0x0220_0x023C[8];
> + u32 hsv_lut_addr;
> + u32 hsv_lut_wdata;
> + u32 hsv_lut_rdata;
> + u32 reserved_0x024C_0x027C[13];
> + u32 cm_coef01_00;
> + u32 cm_coef03_02;
> + u32 cm_coef11_10;
> + u32 cm_coef13_12;
> + u32 cm_coef21_20;
> + u32 cm_coef23_22;
> + u32 reserved_0x0298_0x02BC[10];
> + u32 slp_cfg0;
> + u32 slp_cfg1;
> + u32 reserved_0x02C8_0x02FC[14];
> + u32 gamma_lut_addr;
> + u32 gamma_lut_wdata;
> + u32 gamma_lut_rdata;
> + u32 reserved_0x030C_0x033C[13];
> + u32 checksum_en;
> + u32 checksum0_start_pos;
> + u32 checksum0_end_pos;
> + u32 checksum1_start_pos;
> + u32 checksum1_end_pos;
> + u32 checksum0_result;
> + u32 checksum1_result;
> + u32 reserved_0x035C;
> + u32 dpu_sts[18];
> + u32 reserved_0x03A8_0x03AC[2];
> + u32 dpu_fbc_cfg0;
> + u32 dpu_fbc_cfg1;
> + u32 reserved_0x03B8_0x03EC[14];
> + u32 rf_ram_addr;
> + u32 rf_ram_rdata_low;
> + u32 rf_ram_rdata_high;
> + u32 reserved_0x03FC_0x07FC[257];
> + u32 mmu_en;
> + u32 mmu_update;
> + u32 mmu_min_vpn;
> + u32 mmu_vpn_range;
> + u32 mmu_pt_addr;
> + u32 mmu_default_page;
> + u32 mmu_vaor_addr_rd;
> + u32 mmu_vaor_addr_wr;
> + u32 mmu_inv_addr_rd;
> + u32 mmu_inv_addr_wr;
> + u32 mmu_uns_addr_rd;
> + u32 mmu_uns_addr_wr;
> + u32 mmu_miss_cnt;
> + u32 mmu_pt_update_qos;
> + u32 mmu_version;
> + u32 mmu_min_ppn1;
> + u32 mmu_ppn_range1;
> + u32 mmu_min_ppn2;
> + u32 mmu_ppn_range2;
> + u32 mmu_vpn_paor_rd;
> + u32 mmu_vpn_paor_wr;
> + u32 mmu_ppn_paor_rd;
> + u32 mmu_ppn_paor_wr;
> + u32 mmu_reg_au_manage;
> + u32 mmu_page_rd_ch;
> + u32 mmu_page_wr_ch;
> + u32 mmu_read_page_cmd_cnt;
> + u32 mmu_read_page_latency_cnt;
> + u32 mmu_page_max_latency;
> +};
> +
> +struct wb_region {
> + u32 index;
> + u16 pos_x;
> + u16 pos_y;
> + u16 size_w;
> + u16 size_h;
> +};
> +
> +struct enhance_module {
> + u32 scl_en: 1;
> + u32 epf_en: 1;
> + u32 hsv_en: 1;
> + u32 cm_en: 1;
> + u32 slp_en: 1;
> + u32 gamma_en: 1;
> + u32 blp_en: 1;
> +};
> +
> +struct scale_cfg {
> + u32 in_w;
> + u32 in_h;
> +};
> +
> +struct epf_cfg {
> + u16 epsilon0;
> + u16 epsilon1;
> + u8 gain0;
> + u8 gain1;
> + u8 gain2;
> + u8 gain3;
> + u8 gain4;
> + u8 gain5;
> + u8 gain6;
> + u8 gain7;
> + u8 max_diff;
> + u8 min_diff;
> +};
> +
> +struct hsv_entry {
> + u16 hue;
> + u16 sat;
> +};
> +
> +struct hsv_lut {
> + struct hsv_entry table[360];
> +};
> +
> +struct gamma_entry {
> + u16 r;
> + u16 g;
> + u16 b;
> +};
> +
> +struct gamma_lut {
> + u16 r[256];
> + u16 g[256];
> + u16 b[256];
> +};
> +
> +struct cm_cfg {
> + short coef00;
> + short coef01;
> + short coef02;
> + short coef03;
> + short coef10;
> + short coef11;
> + short coef12;
> + short coef13;
> + short coef20;
> + short coef21;
> + short coef22;
> + short coef23;
> +};
> +
> +struct slp_cfg {
> + u8 brightness;
> + u8 conversion_matrix;
> + u8 brightness_step;
> + u8 second_bright_factor;
> + u8 first_percent_th;
> + u8 first_max_bright_th;
> +};
> +
> +static struct scale_cfg scale_copy;
> +static struct cm_cfg cm_copy;
> +static struct slp_cfg slp_copy;
> +static struct gamma_lut gamma_copy;
> +static struct hsv_lut hsv_copy;
> +static struct epf_cfg epf_copy;
> +static u32 enhance_en;
> +
> +static DECLARE_WAIT_QUEUE_HEAD(wait_queue);
> +static bool panel_ready = true;
> +static bool need_scale;
> +static bool mode_changed;
> +static bool evt_update;
> +static bool evt_stop;
> +static u32 prev_y2r_coef;
> +
> +static void dpu_sr_config(struct dpu_context *ctx);
> +static void dpu_enhance_reload(struct dpu_context *ctx);
> +static void dpu_clean_all(struct dpu_context *ctx);
> +static void dpu_layer(struct dpu_context *ctx,
> + struct sprd_dpu_layer *hwlayer);
> +
> +static u32 dpu_get_version(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + return reg->dpu_version;
> +}
> +
> +static bool dpu_check_raw_int(struct dpu_context *ctx, u32 mask)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + u32 val;
> +
> + down(&ctx->refresh_lock);
> + if (!ctx->is_inited) {
> + up(&ctx->refresh_lock);
> + pr_err("dpu is not initialized\n");
> + return false;
> + }
> +
> + val = reg->dpu_int_raw;
> + up(&ctx->refresh_lock);
> +
> + if (val & mask)
> + return true;
> +
> + pr_err("dpu_int_raw:0x%x\n", val);
> + return false;
> +}
> +
> +static int dpu_parse_dt(struct dpu_context *ctx,
> + struct device_node *np)
> +{
> + return 0;
> +}
> +
> +static void dpu_dump(struct dpu_context *ctx)
> +{
> + u32 *reg = (u32 *)ctx->base;
> + int i;
> +
> + pr_info(" 0 4 8 C\n");
> + for (i = 0; i < 256; i += 4) {
> + pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> + i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i + 3]);
> + }
> +}
> +
> +static u32 check_mmu_isr(struct dpu_context *ctx, u32 reg_val)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + u32 mmu_mask = DISPC_INT_MMU_VAOR_RD_MASK |
> + DISPC_INT_MMU_VAOR_WR_MASK |
> + DISPC_INT_MMU_INV_RD_MASK |
> + DISPC_INT_MMU_INV_WR_MASK;
> + u32 val = reg_val & mmu_mask;
> +
> + if (val) {
> + pr_err("--- iommu interrupt err: 0x%04x ---\n", val);
> +
> + pr_err("iommu invalid read error, addr: 0x%08x\n",
> + reg->mmu_inv_addr_rd);
> + pr_err("iommu invalid write error, addr: 0x%08x\n",
> + reg->mmu_inv_addr_wr);
> + pr_err("iommu va out of range read error, addr: 0x%08x\n",
> + reg->mmu_vaor_addr_rd);
> + pr_err("iommu va out of range write error, addr: 0x%08x\n",
> + reg->mmu_vaor_addr_wr);
> + pr_err("BUG: iommu failure at %s:%d/%s()!\n",
> + __FILE__, __LINE__, __func__);
> +
> + dpu_dump(ctx);
> + }
> +
> + return val;
> +}
> +
> +static u32 dpu_isr(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + u32 reg_val, int_mask = 0;
> +
> + reg_val = reg->dpu_int_sts;
> +
> + /* disable err interrupt */
> + if (reg_val & DISPC_INT_ERR_MASK)
> + int_mask |= DISPC_INT_ERR_MASK;
> +
> + /* dpu update done isr */
> + if (reg_val & DISPC_INT_UPDATE_DONE_MASK) {
> + evt_update = true;
> + wake_up_interruptible_all(&wait_queue);
> + }
> +
> + /* dpu stop done isr */
> + if (reg_val & DISPC_INT_DONE_MASK) {
> + evt_stop = true;
> + wake_up_interruptible_all(&wait_queue);
> + }
> +
> + /* dpu ifbc payload error isr */
> + if (reg_val & DISPC_INT_FBC_PLD_ERR_MASK) {
> + int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
> + pr_err("dpu ifbc payload error\n");
> + }
> +
> + /* dpu ifbc header error isr */
> + if (reg_val & DISPC_INT_FBC_HDR_ERR_MASK) {
> + int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
> + pr_err("dpu ifbc header error\n");
> + }
> +
> + int_mask |= check_mmu_isr(ctx, reg_val);
> +
> + reg->dpu_int_clr = reg_val;
> + reg->dpu_int_en &= ~int_mask;
> +
> + return reg_val;
> +}
> +
> +static int dpu_wait_stop_done(struct dpu_context *ctx)
> +{
> + int rc;
> +
> + if (ctx->is_stopped)
> + return 0;
> +
> + rc = wait_event_interruptible_timeout(wait_queue, evt_stop,
> + msecs_to_jiffies(500));
> + evt_stop = false;
> +
> + ctx->is_stopped = true;
> +
> + if (!rc) {
> + pr_err("dpu wait for stop done time out!\n");
> + return -ETIMEDOUT;
> + }
> +
> + return 0;
> +}
> +
> +static int dpu_wait_update_done(struct dpu_context *ctx)
> +{
> + int rc;
> +
> + evt_update = false;
> +
> + rc = wait_event_interruptible_timeout(wait_queue, evt_update,
> + msecs_to_jiffies(500));
> +
> + if (!rc) {
> + pr_err("dpu wait for reg update done time out!\n");
> + return -ETIMEDOUT;
> + }
> +
> + return 0;
> +}
> +
> +static void dpu_stop(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + if (ctx->if_type == SPRD_DISPC_IF_DPI)
> + reg->dpu_ctrl |= BIT(1);
> +
> + dpu_wait_stop_done(ctx);
> + pr_info("dpu stop\n");
> +}
> +
> +static void dpu_run(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + reg->dpu_ctrl |= BIT(0);
> +
> + ctx->is_stopped = false;
> +
> + pr_info("dpu run\n");
> +
> + if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> + /*
> + * If the panel read GRAM speed faster than
> + * DSI write GRAM speed, it will display some
> + * mass on screen when backlight on. So wait
> + * a TE period after flush the GRAM.
> + */
> + if (!panel_ready) {
> + dpu_wait_stop_done(ctx);
> + /* wait for TE again */
> + mdelay(20);
> + panel_ready = true;
> + }
> + }
> +}
> +
> +static int dpu_init(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + u32 size;
> +
> + reg->bg_color = 0;
> +
> + size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> + reg->panel_size = size;
> + reg->blend_size = size;
> +
> + reg->dpu_cfg0 = BIT(4) | BIT(5);
> + prev_y2r_coef = 3;
> +
> + reg->dpu_cfg1 = 0x004466da;
> + reg->dpu_cfg2 = 0;
> +
> + if (ctx->is_stopped)
> + dpu_clean_all(ctx);
> +
> + reg->mmu_en = 0;
> + reg->mmu_min_ppn1 = 0;
> + reg->mmu_ppn_range1 = 0xffff;
> + reg->mmu_min_ppn2 = 0;
> + reg->mmu_ppn_range2 = 0xffff;
> + reg->mmu_vpn_range = 0x1ffff;
> +
> + reg->dpu_int_clr = 0xffff;
> +
> + dpu_enhance_reload(ctx);
> +
> + return 0;
> +}
> +
> +static void dpu_uninit(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + reg->dpu_int_en = 0;
> + reg->dpu_int_clr = 0xff;
> +
> + panel_ready = false;
> +}
> +
> +enum {
> + DPU_LAYER_FORMAT_YUV422_2PLANE,
> + DPU_LAYER_FORMAT_YUV420_2PLANE,
> + DPU_LAYER_FORMAT_YUV420_3PLANE,
> + DPU_LAYER_FORMAT_ARGB8888,
> + DPU_LAYER_FORMAT_RGB565,
> + DPU_LAYER_FORMAT_XFBC_ARGB8888 = 8,
> + DPU_LAYER_FORMAT_XFBC_RGB565,
> + DPU_LAYER_FORMAT_MAX_TYPES,
> +};
> +
> +enum {
> + DPU_LAYER_ROTATION_0,
> + DPU_LAYER_ROTATION_90,
> + DPU_LAYER_ROTATION_180,
> + DPU_LAYER_ROTATION_270,
> + DPU_LAYER_ROTATION_0_M,
> + DPU_LAYER_ROTATION_90_M,
> + DPU_LAYER_ROTATION_180_M,
> + DPU_LAYER_ROTATION_270_M,
> +};
> +
> +static u32 to_dpu_rotation(u32 angle)
> +{
> + u32 rot = DPU_LAYER_ROTATION_0;
> +
> + switch (angle) {
> + case 0:
> + case DRM_MODE_ROTATE_0:
> + rot = DPU_LAYER_ROTATION_0;
> + break;
> + case DRM_MODE_ROTATE_90:
> + rot = DPU_LAYER_ROTATION_90;
> + break;
> + case DRM_MODE_ROTATE_180:
> + rot = DPU_LAYER_ROTATION_180;
> + break;
> + case DRM_MODE_ROTATE_270:
> + rot = DPU_LAYER_ROTATION_270;
> + break;
> + case DRM_MODE_REFLECT_Y:
> + rot = DPU_LAYER_ROTATION_180_M;
> + break;
> + case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> + rot = DPU_LAYER_ROTATION_90_M;
> + break;
> + case DRM_MODE_REFLECT_X:
> + rot = DPU_LAYER_ROTATION_0_M;
> + break;
> + case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> + rot = DPU_LAYER_ROTATION_270_M;
> + break;
> + default:
> + pr_err("rotation convert unsupport angle (drm)= 0x%x\n", angle);
> + break;
> + }
> +
> + return rot;
> +}
> +
> +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 compression, u32 rotation)
> +{
> + int reg_val = 0;
> +
> + /* layer enable */
> + reg_val |= BIT(0);
> +
> + switch (format) {
> + case DRM_FORMAT_BGRA8888:
> + /* BGRA8888 -> ARGB8888 */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> + if (compression)
> + /* XFBC-ARGB8888 */
> + reg_val |= (DPU_LAYER_FORMAT_XFBC_ARGB8888 << 4);
> + else
> + reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> + break;
> + case DRM_FORMAT_RGBX8888:
> + case DRM_FORMAT_RGBA8888:
> + /* RGBA8888 -> ABGR8888 */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> + case DRM_FORMAT_ABGR8888:
> + /* rb switch */
> + reg_val |= BIT(10);
> + case DRM_FORMAT_ARGB8888:
> + if (compression)
> + /* XFBC-ARGB8888 */
> + reg_val |= (DPU_LAYER_FORMAT_XFBC_ARGB8888 << 4);
> + else
> + reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> + break;
> + case DRM_FORMAT_XBGR8888:
> + /* rb switch */
> + reg_val |= BIT(10);
> + case DRM_FORMAT_XRGB8888:
> + if (compression)
> + /* XFBC-ARGB8888 */
> + reg_val |= (DPU_LAYER_FORMAT_XFBC_ARGB8888 << 4);
> + else
> + reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> + break;
> + case DRM_FORMAT_BGR565:
> + /* rb switch */
> + reg_val |= BIT(10);
> + case DRM_FORMAT_RGB565:
> + if (compression)
> + /* XFBC-RGB565 */
> + reg_val |= (DPU_LAYER_FORMAT_XFBC_RGB565 << 4);
> + else
> + reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> + break;
> + case DRM_FORMAT_NV12:
> + /* 2-Lane: Yuv420 */
> + reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> + /* Y endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> + /* UV endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> + break;
> + case DRM_FORMAT_NV21:
> + /* 2-Lane: Yuv420 */
> + reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> + /* Y endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> + /* UV endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> + break;
> + case DRM_FORMAT_NV16:
> + /* 2-Lane: Yuv422 */
> + reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> + /* Y endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> + /* UV endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> + break;
> + case DRM_FORMAT_NV61:
> + /* 2-Lane: Yuv422 */
> + reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> + /* Y endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> + /* UV endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> + break;
> + case DRM_FORMAT_YUV420:
> + reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> + /* Y endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> + /* UV endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> + break;
> + case DRM_FORMAT_YVU420:
> + reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> + /* Y endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> + /* UV endian */
> + reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> + break;
> + default:
> + pr_err("error: invalid format %c%c%c%c\n", format,
> + format >> 8,
> + format >> 16,
> + format >> 24);
> + break;
> + }
> +
> + switch (blending) {
> + case DRM_MODE_BLEND_PIXEL_NONE:
> + /* don't do blending, maybe RGBX */
> + /* alpha mode select - layer alpha */
> + reg_val |= BIT(2);
> + break;
> + case DRM_MODE_BLEND_COVERAGE:
> + /* alpha mode select - combo alpha */
> + reg_val |= BIT(3);
> + /*Normal mode*/
> + reg_val &= (~BIT(16));
> + break;
> + case DRM_MODE_BLEND_PREMULTI:
> + /* alpha mode select - combo alpha */
> + reg_val |= BIT(3);
> + /*Pre-mult mode*/
> + reg_val |= BIT(16);
> + break;
> + default:
> + /* alpha mode select - layer alpha */
> + reg_val |= BIT(2);
> + break;
> + }
> +
> + rotation = to_dpu_rotation(rotation);
> + reg_val |= (rotation & 0x7) << 20;
> +
> + return reg_val;
> +}
> +
> +static int check_layer_y2r_coef(struct sprd_dpu_layer layers[], u8 count)
> +{
> + int i;
> +
> + for (i = (count - 1); i >= 0; i--) {
> + switch (layers[i].format) {
> + case DRM_FORMAT_NV12:
> + case DRM_FORMAT_NV21:
> + case DRM_FORMAT_NV16:
> + case DRM_FORMAT_NV61:
> + case DRM_FORMAT_YUV420:
> + case DRM_FORMAT_YVU420:
> + if (layers[i].y2r_coef == prev_y2r_coef)
> + return -EINVAL;
> +
> + /* need to config dpu y2r coef */
> + prev_y2r_coef = layers[i].y2r_coef;
> + return prev_y2r_coef;
> + default:
> + break;
> + }
> + }
> +
> + /* not find yuv layer */
> + return -EINVAL;
> +}
> +
> +static void dpu_clean_all(struct dpu_context *ctx)
> +{
> + int i;
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + for (i = 0; i < 8; i++)
> + reg->layers[i].ctrl = 0;
> +}
> +
> +static void dpu_bgcolor(struct dpu_context *ctx, u32 color)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> + dpu_wait_stop_done(ctx);
> +
> + reg->bg_color = color;
> +
> + dpu_clean_all(ctx);
> +
> + if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->is_stopped) {
> + reg->dpu_ctrl |= BIT(2);
> + dpu_wait_update_done(ctx);
> + } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> + reg->dpu_ctrl |= BIT(0);
> + ctx->is_stopped = false;
> + }
> +}
> +
> +static void dpu_layer(struct dpu_context *ctx,
> + struct sprd_dpu_layer *hwlayer)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + const struct drm_format_info *info;
> + struct layer_reg *layer;
> + u32 addr, size, offset;
> + int i;
> +
> + layer = &reg->layers[hwlayer->index];
> + offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> +
> + if (hwlayer->pallete_en) {
> + size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
> + layer->pos = offset;
> + layer->size = size;
> + layer->alpha = hwlayer->alpha;
> + layer->pallete = hwlayer->pallete_color;
> +
> + /* pallete layer enable */
> + layer->ctrl = 0x1005;
> +
> + pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
> + hwlayer->dst_x, hwlayer->dst_y,
> + hwlayer->dst_w, hwlayer->dst_h);
> + return;
> + }
> +
> + if (hwlayer->src_w && hwlayer->src_h)
> + size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) << 16);
> + else
> + size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
> +
> + for (i = 0; i < hwlayer->planes; i++) {
> + addr = hwlayer->addr[i];
> +
> + /* dpu r2p0 just support xfbc-rgb */
> + if (hwlayer->xfbc)
> + addr += hwlayer->header_size_r;
> +
> + if (addr % 16)
> + pr_err("layer addr[%d] is not 16 bytes align, it's 0x%08x\n",
> + i, addr);
> + layer->addr[i] = addr;
> + }
> +
> + layer->pos = offset;
> + layer->size = size;
> + layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> + layer->alpha = hwlayer->alpha;
> +
> + info = drm_format_info(hwlayer->format);
> + if (info->cpp[0] == 0) {
> + pr_err("layer[%d] bytes per pixel is invalid\n", hwlayer->index);
> + return;
> + }
> +
> + if (hwlayer->planes == 3)
> + /* UV pitch is 1/2 of Y pitch*/
> + layer->pitch = (hwlayer->pitch[0] / info->cpp[0]) |
> + (hwlayer->pitch[0] / info->cpp[0] << 15);
> + else
> + layer->pitch = hwlayer->pitch[0] / info->cpp[0];
> +
> + layer->ctrl = dpu_img_ctrl(hwlayer->format, hwlayer->blending,
> + hwlayer->xfbc, hwlayer->rotation);
> +
> + pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
> + hwlayer->dst_x, hwlayer->dst_y,
> + hwlayer->dst_w, hwlayer->dst_h);
> + pr_debug("start_x = %d, start_y = %d, start_w = %d, start_h = %d\n",
> + hwlayer->src_x, hwlayer->src_y,
> + hwlayer->src_w, hwlayer->src_h);
> +}
> +
> +static void dpu_scaling(struct dpu_context *ctx,
> + struct sprd_dpu_layer layers[], u8 count)
> +{
> + int i;
> + struct sprd_dpu_layer *top_layer;
> +
> + if (mode_changed) {
> + top_layer = &layers[count - 1];
> + pr_debug("------------------------------------\n");
> + for (i = 0; i < count; i++) {
> + pr_debug("layer[%d] : %dx%d --- (%d)\n", i,
> + layers[i].dst_w, layers[i].dst_h,
> + scale_copy.in_w);
> + }
> +
> + if (top_layer->dst_w <= scale_copy.in_w) {
> + dpu_sr_config(ctx);
> + mode_changed = false;
> +
> + pr_info("do scaling enhace: 0x%x, top layer(%dx%d)\n",
> + enhance_en, top_layer->dst_w,
> + top_layer->dst_h);
> + }
> + }
> +}
> +
> +static void dpu_flip(struct dpu_context *ctx,
> + struct sprd_dpu_layer layers[], u8 count)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + int i;
> + int y2r_coef;
> +
> + /*
> + * Make sure the dpu is in stop status. DPU_R2P0 has no shadow
> + * registers in EDPI mode. So the config registers can only be
> + * updated in the rising edge of DPU_RUN bit.
> + */
> + if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> + dpu_wait_stop_done(ctx);
> +
> + /* set Y2R conversion coef */
> + y2r_coef = check_layer_y2r_coef(layers, count);
> + if (y2r_coef >= 0) {
> + /* write dpu_cfg0 register after dpu is in idle status */
> + if (ctx->if_type == SPRD_DISPC_IF_DPI)
> + dpu_stop(ctx);
> +
> + reg->dpu_cfg0 &= ~(0x7 << 4);
> + reg->dpu_cfg0 |= (y2r_coef << 4);
> + }
> +
> + /* reset the bgcolor to black */
> + reg->bg_color = 0;
> +
> + /* disable all the layers */
> + dpu_clean_all(ctx);
> +
> + /* to check if dpu need scaling the frame for SR */
> + dpu_scaling(ctx, layers, count);
> +
> + /* start configure dpu layers */
> + for (i = 0; i < count; i++)
> + dpu_layer(ctx, &layers[i]);
> +
> + /* update trigger and wait */
> + if (ctx->if_type == SPRD_DISPC_IF_DPI) {
> + if (!ctx->is_stopped) {
> + reg->dpu_ctrl |= BIT(2);
> + dpu_wait_update_done(ctx);
> + } else if (y2r_coef >= 0) {
> + reg->dpu_ctrl |= BIT(0);
> + ctx->is_stopped = false;
> + pr_info("dpu start\n");
> + }
> +
> + reg->dpu_int_en |= DISPC_INT_ERR_MASK;
> +
> + } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> + reg->dpu_ctrl |= BIT(0);
> +
> + ctx->is_stopped = false;
> + }
> +
> + /*
> + * If the following interrupt was disabled in isr,
> + * re-enable it.
> + */
> + reg->dpu_int_en |= DISPC_INT_FBC_PLD_ERR_MASK |
> + DISPC_INT_FBC_HDR_ERR_MASK |
> + DISPC_INT_MMU_VAOR_RD_MASK |
> + DISPC_INT_MMU_VAOR_WR_MASK |
> + DISPC_INT_MMU_INV_RD_MASK |
> + DISPC_INT_MMU_INV_WR_MASK;
> +}
> +
> +static void dpu_dpi_init(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + u32 int_mask = 0;
> +
> + if (ctx->if_type == SPRD_DISPC_IF_DPI) {
> + /* use dpi as interface */
> + reg->dpu_cfg0 &= ~BIT(0);
> +
> + /* disable Halt function for SPRD DSI */
> + reg->dpi_ctrl &= ~BIT(16);
> +
> + /* select te from external pad */
> + reg->dpi_ctrl |= BIT(10);
> +
> + /* set dpi timing */
> + reg->dpi_h_timing = (ctx->vm.hsync_len << 0) |
> + (ctx->vm.hback_porch << 8) |
> + (ctx->vm.hfront_porch << 20);
> + reg->dpi_v_timing = (ctx->vm.vsync_len << 0) |
> + (ctx->vm.vback_porch << 8) |
> + (ctx->vm.vfront_porch << 20);
> + if (ctx->vm.vsync_len + ctx->vm.vback_porch < 32)
> + pr_warn("Warning: (vsync + vbp) < 32, "
> + "underflow risk!\n");
> +
> + /* enable dpu update done INT */
> + int_mask |= DISPC_INT_UPDATE_DONE_MASK;
> + /* enable dpu DONE INT */
> + int_mask |= DISPC_INT_DONE_MASK;
> + /* enable dpu dpi vsync */
> + int_mask |= DISPC_INT_DPI_VSYNC_MASK;
> + /* enable dpu TE INT */
> + int_mask |= DISPC_INT_TE_MASK;
> + /* enable underflow err INT */
> + int_mask |= DISPC_INT_ERR_MASK;
> + /* enable write back done INT */
> + int_mask |= DISPC_INT_WB_DONE_MASK;
> + /* enable write back fail INT */
> + int_mask |= DISPC_INT_WB_FAIL_MASK;
> +
> + } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> + /* use edpi as interface */
> + reg->dpu_cfg0 |= BIT(0);
> +
> + /* use external te */
> + reg->dpi_ctrl |= BIT(10);
> +
> + /* enable te */
> + reg->dpi_ctrl |= BIT(8);
> +
> + /* enable stop DONE INT */
> + int_mask |= DISPC_INT_DONE_MASK;
> + /* enable TE INT */
> + int_mask |= DISPC_INT_TE_MASK;
> + }
> +
> + /* enable ifbc payload error INT */
> + int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
> + /* enable ifbc header error INT */
> + int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
> + /* enable iommu va out of range read error INT */
> + int_mask |= DISPC_INT_MMU_VAOR_RD_MASK;
> + /* enable iommu va out of range write error INT */
> + int_mask |= DISPC_INT_MMU_VAOR_WR_MASK;
> + /* enable iommu invalid read error INT */
> + int_mask |= DISPC_INT_MMU_INV_RD_MASK;
> + /* enable iommu invalid write error INT */
> + int_mask |= DISPC_INT_MMU_INV_WR_MASK;
> +
> + reg->dpu_int_en = int_mask;
> +}
> +
> +static void enable_vsync(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + reg->dpu_int_en |= DISPC_INT_DPI_VSYNC_MASK;
> +}
> +
> +static void disable_vsync(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + reg->dpu_int_en &= ~DISPC_INT_DPI_VSYNC_MASK;
> +}
> +
> +static void dpu_enhance_backup(u32 id, void *param)
> +{
> + u32 *p;
> +
> + switch (id) {
> + case ENHANCE_CFG_ID_ENABLE:
> + p = param;
> + enhance_en |= *p;
> + pr_info("enhance enable backup: 0x%x\n", *p);
> + break;
> + case ENHANCE_CFG_ID_DISABLE:
> + p = param;
> + enhance_en &= ~(*p);
> + if (*p & BIT(1))
> + memset(&epf_copy, 0, sizeof(epf_copy));
> + pr_info("enhance disable backup: 0x%x\n", *p);
> + break;
> + case ENHANCE_CFG_ID_SCL:
> + memcpy(&scale_copy, param, sizeof(scale_copy));
> + enhance_en |= BIT(0);
> + pr_info("enhance scaling backup\n");
> + break;
> + case ENHANCE_CFG_ID_HSV:
> + memcpy(&hsv_copy, param, sizeof(hsv_copy));
> + enhance_en |= BIT(2);
> + pr_info("enhance hsv backup\n");
> + break;
> + case ENHANCE_CFG_ID_CM:
> + memcpy(&cm_copy, param, sizeof(cm_copy));
> + enhance_en |= BIT(3);
> + pr_info("enhance cm backup\n");
> + break;
> + case ENHANCE_CFG_ID_SLP:
> + memcpy(&slp_copy, param, sizeof(slp_copy));
> + enhance_en |= BIT(4);
> + pr_info("enhance slp backup\n");
> + break;
> + case ENHANCE_CFG_ID_GAMMA:
> + memcpy(&gamma_copy, param, sizeof(gamma_copy));
> + enhance_en |= BIT(5);
> + pr_info("enhance gamma backup\n");
> + break;
> + case ENHANCE_CFG_ID_EPF:
> + memcpy(&epf_copy, param, sizeof(epf_copy));
> + if ((slp_copy.brightness > SLP_BRIGHTNESS_THRESHOLD) ||
> + (enhance_en & BIT(0))) {
> + enhance_en |= BIT(1);
> + pr_info("enhance epf backup\n");
> + }
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static void dpu_epf_set(struct dpu_reg *reg, struct epf_cfg *epf)
> +{
> + reg->epf_epsilon = (epf->epsilon1 << 16) | epf->epsilon0;
> + reg->epf_gain0_3 = (epf->gain3 << 24) | (epf->gain2 << 16) |
> + (epf->gain1 << 8) | epf->gain0;
> + reg->epf_gain4_7 = (epf->gain7 << 24) | (epf->gain6 << 16) |
> + (epf->gain5 << 8) | epf->gain4;
> + reg->epf_diff = (epf->max_diff << 8) | epf->min_diff;
> +}
> +
> +static void dpu_enhance_set(struct dpu_context *ctx, u32 id, void *param)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + struct scale_cfg *scale;
> + struct cm_cfg *cm;
> + struct slp_cfg *slp;
> + struct gamma_lut *gamma;
> + struct hsv_lut *hsv;
> + struct epf_cfg *epf;
> + u32 *p, i;
> +
> + if (!ctx->is_inited) {
> + dpu_enhance_backup(id, param);
> + return;
> + }
> +
> + if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> + dpu_wait_stop_done(ctx);
> +
> + switch (id) {
> + case ENHANCE_CFG_ID_ENABLE:
> + p = param;
> + reg->dpu_enhance_cfg |= *p;
> + pr_info("enhance module enable: 0x%x\n", *p);
> + break;
> + case ENHANCE_CFG_ID_DISABLE:
> + p = param;
> + reg->dpu_enhance_cfg &= ~(*p);
> + if (*p & BIT(1))
> + memset(&epf_copy, 0, sizeof(epf_copy));
> + pr_info("enhance module disable: 0x%x\n", *p);
> + break;
> + case ENHANCE_CFG_ID_SCL:
> + memcpy(&scale_copy, param, sizeof(scale_copy));
> + scale = &scale_copy;
> + reg->blend_size = (scale->in_h << 16) | scale->in_w;
> + reg->dpu_enhance_cfg |= BIT(0);
> + pr_info("enhance scaling: %ux%u\n", scale->in_w, scale->in_h);
> + break;
> + case ENHANCE_CFG_ID_HSV:
> + memcpy(&hsv_copy, param, sizeof(hsv_copy));
> + hsv = &hsv_copy;
> + for (i = 0; i < 360; i++) {
> + reg->hsv_lut_addr = i;
> + udelay(1);
> + reg->hsv_lut_wdata = (hsv->table[i].sat << 16) |
> + hsv->table[i].hue;
> + }
> + reg->dpu_enhance_cfg |= BIT(2);
> + pr_info("enhance hsv set\n");
> + break;
> + case ENHANCE_CFG_ID_CM:
> + memcpy(&cm_copy, param, sizeof(cm_copy));
> + cm = &cm_copy;
> + reg->cm_coef01_00 = (cm->coef01 << 16) | cm->coef00;
> + reg->cm_coef03_02 = (cm->coef03 << 16) | cm->coef02;
> + reg->cm_coef11_10 = (cm->coef11 << 16) | cm->coef10;
> + reg->cm_coef13_12 = (cm->coef13 << 16) | cm->coef12;
> + reg->cm_coef21_20 = (cm->coef21 << 16) | cm->coef20;
> + reg->cm_coef23_22 = (cm->coef23 << 16) | cm->coef22;
> + reg->dpu_enhance_cfg |= BIT(3);
> + pr_info("enhance cm set\n");
> + break;
> + case ENHANCE_CFG_ID_SLP:
> + memcpy(&slp_copy, param, sizeof(slp_copy));
> + slp = &slp_copy;
> + reg->slp_cfg0 = (slp->second_bright_factor << 24) |
> + (slp->brightness_step << 16) |
> + (slp->conversion_matrix << 8) |
> + slp->brightness;
> + reg->slp_cfg1 = (slp->first_max_bright_th << 8) |
> + slp->first_percent_th;
> + reg->dpu_enhance_cfg |= BIT(4);
> + pr_info("enhance slp set\n");
> + break;
> + case ENHANCE_CFG_ID_GAMMA:
> + memcpy(&gamma_copy, param, sizeof(gamma_copy));
> + gamma = &gamma_copy;
> + for (i = 0; i < 256; i++) {
> + reg->gamma_lut_addr = i;
> + udelay(1);
> + reg->gamma_lut_wdata = (gamma->r[i] << 20) |
> + (gamma->g[i] << 10) |
> + gamma->b[i];
> + pr_debug("0x%02x: r=%u, g=%u, b=%u\n", i,
> + gamma->r[i], gamma->g[i], gamma->b[i]);
> + }
> + reg->dpu_enhance_cfg |= BIT(5);
> + pr_info("enhance gamma set\n");
> + break;
> + case ENHANCE_CFG_ID_EPF:
> + memcpy(&epf_copy, param, sizeof(epf_copy));
> + if ((slp_copy.brightness > SLP_BRIGHTNESS_THRESHOLD) ||
> + (enhance_en & BIT(0))) {
> + epf = &epf_copy;
> + dpu_epf_set(reg, epf);
> + reg->dpu_enhance_cfg |= BIT(1);
> + pr_info("enhance epf set\n");
> + break;
> + }
> + return;
> + default:
> + break;
> + }
> +
> + if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->is_stopped) {
> + reg->dpu_ctrl |= BIT(2);
> + dpu_wait_update_done(ctx);
> + } else if ((ctx->if_type == SPRD_DISPC_IF_EDPI) && panel_ready) {
> + /*
> + * In EDPI mode, we need to wait panel initializatin
> + * completed. Otherwise, the dpu enhance settings may
> + * start before panel initialization.
> + */
> + reg->dpu_ctrl |= BIT(0);
> + ctx->is_stopped = false;
> + }
> +
> + enhance_en = reg->dpu_enhance_cfg;
> +}
> +
> +static void dpu_enhance_get(struct dpu_context *ctx, u32 id, void *param)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + struct scale_cfg *scale;
> + struct epf_cfg *ep;
> + struct slp_cfg *slp;
> + struct gamma_lut *gamma;
> + u32 *p32;
> + int i, val;
> +
> + switch (id) {
> + case ENHANCE_CFG_ID_ENABLE:
> + p32 = param;
> + *p32 = reg->dpu_enhance_cfg;
> + pr_info("enhance module enable get\n");
> + break;
> + case ENHANCE_CFG_ID_SCL:
> + scale = param;
> + val = reg->blend_size;
> + scale->in_w = val & 0xffff;
> + scale->in_h = val >> 16;
> + pr_info("enhance scaling get\n");
> + break;
> + case ENHANCE_CFG_ID_EPF:
> + ep = param;
> +
> + val = reg->epf_epsilon;
> + ep->epsilon0 = val;
> + ep->epsilon1 = val >> 16;
> +
> + val = reg->epf_gain0_3;
> + ep->gain0 = val;
> + ep->gain1 = val >> 8;
> + ep->gain2 = val >> 16;
> + ep->gain3 = val >> 24;
> +
> + val = reg->epf_gain4_7;
> + ep->gain4 = val;
> + ep->gain5 = val >> 8;
> + ep->gain6 = val >> 16;
> + ep->gain7 = val >> 24;
> +
> + val = reg->epf_diff;
> + ep->min_diff = val;
> + ep->max_diff = val >> 8;
> + pr_info("enhance epf get\n");
> + break;
> + case ENHANCE_CFG_ID_HSV:
> + dpu_stop(ctx);
> + p32 = param;
> + for (i = 0; i < 360; i++) {
> + reg->hsv_lut_addr = i;
> + udelay(1);
> + *p32++ = reg->hsv_lut_rdata;
> + }
> + dpu_run(ctx);
> + pr_info("enhance hsv get\n");
> + break;
> + case ENHANCE_CFG_ID_CM:
> + p32 = param;
> + *p32++ = reg->cm_coef01_00;
> + *p32++ = reg->cm_coef03_02;
> + *p32++ = reg->cm_coef11_10;
> + *p32++ = reg->cm_coef13_12;
> + *p32++ = reg->cm_coef21_20;
> + *p32++ = reg->cm_coef23_22;
> + pr_info("enhance cm get\n");
> + break;
> + case ENHANCE_CFG_ID_SLP:
> + slp = param;
> +
> + val = reg->slp_cfg0;
> + slp->brightness = val;
> + slp->conversion_matrix = val >> 8;
> + slp->brightness_step = val >> 16;
> + slp->second_bright_factor = val >> 24;
> +
> + val = reg->slp_cfg1;
> + slp->first_percent_th = val;
> + slp->first_max_bright_th = val >> 8;
> + pr_info("enhance slp get\n");
> + break;
> + case ENHANCE_CFG_ID_GAMMA:
> + dpu_stop(ctx);
> + gamma = param;
> + for (i = 0; i < 256; i++) {
> + reg->gamma_lut_addr = i;
> + udelay(1);
> + val = reg->gamma_lut_rdata;
> + gamma->r[i] = (val >> 20) & 0x3FF;
> + gamma->g[i] = (val >> 10) & 0x3FF;
> + gamma->b[i] = val & 0x3FF;
> + pr_debug("0x%02x: r=%u, g=%u, b=%u\n", i,
> + gamma->r[i], gamma->g[i], gamma->b[i]);
> + }
> + dpu_run(ctx);
> + pr_info("enhance gamma get\n");
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static void dpu_enhance_reload(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> + struct scale_cfg *scale;
> + struct cm_cfg *cm;
> + struct slp_cfg *slp;
> + struct gamma_lut *gamma;
> + struct hsv_lut *hsv;
> + struct epf_cfg *epf;
> + int i;
> +
> + if (enhance_en & BIT(0)) {
> + scale = &scale_copy;
> + reg->blend_size = (scale->in_h << 16) | scale->in_w;
> + pr_info("enhance scaling from %ux%u to %ux%u\n", scale->in_w,
> + scale->in_h, ctx->vm.hactive, ctx->vm.vactive);
> + }
> +
> + if (enhance_en & BIT(1)) {
> + epf = &epf_copy;
> + dpu_epf_set(reg, epf);
> + pr_info("enhance epf reload\n");
> + }
> +
> + if (enhance_en & BIT(2)) {
> + hsv = &hsv_copy;
> + for (i = 0; i < 360; i++) {
> + reg->hsv_lut_addr = i;
> + udelay(1);
> + reg->hsv_lut_wdata = (hsv->table[i].sat << 16) |
> + hsv->table[i].hue;
> + }
> + pr_info("enhance hsv reload\n");
> + }
> +
> + if (enhance_en & BIT(3)) {
> + cm = &cm_copy;
> + reg->cm_coef01_00 = (cm->coef01 << 16) | cm->coef00;
> + reg->cm_coef03_02 = (cm->coef03 << 16) | cm->coef02;
> + reg->cm_coef11_10 = (cm->coef11 << 16) | cm->coef10;
> + reg->cm_coef13_12 = (cm->coef13 << 16) | cm->coef12;
> + reg->cm_coef21_20 = (cm->coef21 << 16) | cm->coef20;
> + reg->cm_coef23_22 = (cm->coef23 << 16) | cm->coef22;
> + pr_info("enhance cm reload\n");
> + }
> +
> + if (enhance_en & BIT(4)) {
> + slp = &slp_copy;
> + reg->slp_cfg0 = (slp->second_bright_factor << 24) |
> + (slp->brightness_step << 16) |
> + (slp->conversion_matrix << 8) |
> + slp->brightness;
> + reg->slp_cfg1 = (slp->first_max_bright_th << 8) |
> + slp->first_percent_th;
> + pr_info("enhance slp reload\n");
> + }
> +
> + if (enhance_en & BIT(5)) {
> + gamma = &gamma_copy;
> + for (i = 0; i < 256; i++) {
> + reg->gamma_lut_addr = i;
> + udelay(1);
> + reg->gamma_lut_wdata = (gamma->r[i] << 20) |
> + (gamma->g[i] << 10) |
> + gamma->b[i];
> + pr_debug("0x%02x: r=%u, g=%u, b=%u\n", i,
> + gamma->r[i], gamma->g[i], gamma->b[i]);
> + }
> + pr_info("enhance gamma reload\n");
> + }
> +
> + reg->dpu_enhance_cfg = enhance_en;
> +}
> +
> +static void dpu_sr_config(struct dpu_context *ctx)
> +{
> + struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> + reg->blend_size = (scale_copy.in_h << 16) | scale_copy.in_w;
> + if (need_scale) {
> + /* SLP is disabled mode or bypass mode */
> + if (slp_copy.brightness <= SLP_BRIGHTNESS_THRESHOLD) {
> +
> + /*
> + * valid range of gain3 is [128,255];dpu_scaling maybe
> + * called before epf_copy is assinged a value
> + */
> + if (epf_copy.gain3 > 0) {
> + dpu_epf_set(reg, &epf_copy);
> + enhance_en |= BIT(1);
> + }
> + }
> + enhance_en |= BIT(0);
> + reg->dpu_enhance_cfg = enhance_en;
> + } else {
> + if (slp_copy.brightness <= SLP_BRIGHTNESS_THRESHOLD)
> + enhance_en &= ~(BIT(1));
> +
> + enhance_en &= ~(BIT(0));
> + reg->dpu_enhance_cfg = enhance_en;
> + }
> +}
> +
> +static int dpu_modeset(struct dpu_context *ctx,
> + struct drm_mode_modeinfo *mode)
> +{
> + scale_copy.in_w = mode->hdisplay;
> + scale_copy.in_h = mode->vdisplay;
> +
> + if ((mode->hdisplay != ctx->vm.hactive) ||
> + (mode->vdisplay != ctx->vm.vactive))
> + need_scale = true;
> + else
> + need_scale = false;
> +
> + mode_changed = true;
> + pr_info("begin switch to %u x %u\n", mode->hdisplay, mode->vdisplay);
> +
> + return 0;
> +}
> +
> +static const u32 primary_fmts[] = {
> + DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
> + DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
> + DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
> + DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
> + DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
> + DRM_FORMAT_NV12, DRM_FORMAT_NV21,
> + DRM_FORMAT_NV16, DRM_FORMAT_NV61,
> + DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
> +};
> +
> +static int dpu_capability(struct dpu_context *ctx,
> + struct dpu_capability *cap)
> +{
> + if (!cap)
> + return -EINVAL;
> +
> + cap->max_layers = 6;
> + cap->fmts_ptr = primary_fmts;
> + cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> +
> + return 0;
> +}
> +
> +static struct dpu_core_ops dpu_r2p0_ops = {
> + .parse_dt = dpu_parse_dt,
> + .version = dpu_get_version,
> + .init = dpu_init,
> + .uninit = dpu_uninit,
> + .run = dpu_run,
> + .stop = dpu_stop,
> + .isr = dpu_isr,
> + .ifconfig = dpu_dpi_init,
> + .capability = dpu_capability,
> + .flip = dpu_flip,
> + .bg_color = dpu_bgcolor,
> + .enable_vsync = enable_vsync,
> + .disable_vsync = disable_vsync,
> + .enhance_set = dpu_enhance_set,
> + .enhance_get = dpu_enhance_get,
> + .modeset = dpu_modeset,
> + .check_raw_int = dpu_check_raw_int,
> +};
> +
> +static struct ops_entry entry = {
> + .ver = "dpu-r2p0",
> + .ops = &dpu_r2p0_ops,
> +};
> +
> +static int __init dpu_core_register(void)
> +{
> + return dpu_core_ops_register(&entry);
> +}
> +
> +subsys_initcall(dpu_core_register);
> diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c b/drivers/gpu/drm/sprd/sprd_dpu.c
> new file mode 100644
> index 0000000..43142b3
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_dpu.c
> @@ -0,0 +1,1152 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#include <linux/component.h>
> +#include <linux/dma-buf.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_plane_helper.h>
> +
> +#include "sprd_drm.h"
> +#include "sprd_dpu.h"
> +#include "sprd_gem.h"
> +
> +struct sprd_plane {
> + struct drm_plane plane;
> + struct drm_property *alpha_property;
> + struct drm_property *blend_mode_property;
> + struct drm_property *fbc_hsize_r_property;
> + struct drm_property *fbc_hsize_y_property;
> + struct drm_property *fbc_hsize_uv_property;
> + struct drm_property *y2r_coef_property;
> + struct drm_property *pallete_en_property;
> + struct drm_property *pallete_color_property;
> + u32 index;
> +};
> +
> +struct sprd_plane_state {
> + struct drm_plane_state state;
> + u8 alpha;
> + u8 blend_mode;
> + u32 fbc_hsize_r;
> + u32 fbc_hsize_y;
> + u32 fbc_hsize_uv;
> + u32 y2r_coef;
> + u32 pallete_en;
> + u32 pallete_color;
> +};
> +
> +LIST_HEAD(dpu_core_head);
> +LIST_HEAD(dpu_clk_head);
> +LIST_HEAD(dpu_glb_head);
> +
> +static int sprd_dpu_init(struct sprd_dpu *dpu);
> +static int sprd_dpu_uninit(struct sprd_dpu *dpu);
> +
> +static inline struct sprd_plane *to_sprd_plane(struct drm_plane *plane)
> +{
> + return container_of(plane, struct sprd_plane, plane);
> +}
> +
> +static inline struct
> +sprd_plane_state *to_sprd_plane_state(const struct drm_plane_state *state)
> +{
> + return container_of(state, struct sprd_plane_state, state);
> +}
> +
> +static int sprd_dpu_iommu_map(struct device *dev,
> + struct sprd_gem_obj *sprd_gem)
> +{
> + /*
> + * TODO:
> + * Currently only supports dumb buffer,
> + * will support iommu in the future.
> + */
> +
> + return 0;
> +}
> +
> +static void sprd_dpu_iommu_unmap(struct device *dev,
> + struct sprd_gem_obj *sprd_gem)
> +{
> + /*
> + * TODO:
> + * Currently only supports dumb buffer,
> + * will support iommu in the future.
> + */
> +}
> +
> +static int of_get_logo_memory_info(struct sprd_dpu *dpu,
> + struct device_node *np)
> +{
> + struct device_node *node;
> + struct resource r;
> + int ret;
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + node = of_parse_phandle(np, "sprd,logo-memory", 0);
> + if (!node) {
> + DRM_INFO("no sprd,logo-memory specified\n");
> + return 0;
> + }
> +
> + ret = of_address_to_resource(node, 0, &r);
> + of_node_put(node);
> + if (ret) {
> + DRM_ERROR("invalid logo reserved memory node!\n");
> + return -EINVAL;
> + }
> +
> + ctx->logo_addr = r.start;
> + ctx->logo_size = resource_size(&r);
> +
> + return 0;
> +}
> +
> +static int sprd_plane_prepare_fb(struct drm_plane *plane,
> + struct drm_plane_state *new_state)
> +{
> + struct drm_plane_state *curr_state = plane->state;
> + struct drm_framebuffer *fb;
> + struct drm_gem_object *obj;
> + struct sprd_gem_obj *sprd_gem;
> + struct sprd_dpu *dpu;
> + int i;
> +
> + if ((curr_state->fb == new_state->fb) || !new_state->fb)
> + return 0;
> +
> + fb = new_state->fb;
> + dpu = crtc_to_dpu(new_state->crtc);
> +
> + if (!dpu->ctx.is_inited) {
> + DRM_WARN("dpu has already powered off\n");
> + return 0;
> + }
> +
> + for (i = 0; i < fb->format->num_planes; i++) {
> + obj = drm_gem_fb_get_obj(fb, i);
> + sprd_gem = to_sprd_gem_obj(obj);
> + if (sprd_gem->sgtb && sprd_gem->sgtb->nents > 1)
> + sprd_dpu_iommu_map(&dpu->dev, sprd_gem);
> + }
> +
> + return 0;
> +}
> +
> +static void sprd_plane_cleanup_fb(struct drm_plane *plane,
> + struct drm_plane_state *old_state)
> +{
> + struct drm_plane_state *curr_state = plane->state;
> + struct drm_framebuffer *fb;
> + struct drm_gem_object *obj;
> + struct sprd_gem_obj *sprd_gem;
> + struct sprd_dpu *dpu;
> + int i;
> + static atomic_t logo2animation = { -1 };
> +
> + if ((curr_state->fb == old_state->fb) || !old_state->fb)
> + return;
> +
> + fb = old_state->fb;
> + dpu = crtc_to_dpu(old_state->crtc);
> +
> + if (!dpu->ctx.is_inited) {
> + DRM_WARN("dpu has already powered off\n");
> + return;
> + }
> +
> + for (i = 0; i < fb->format->num_planes; i++) {
> + obj = drm_gem_fb_get_obj(fb, i);
> + sprd_gem = to_sprd_gem_obj(obj);
> + if (sprd_gem->sgtb && sprd_gem->sgtb->nents > 1)
> + sprd_dpu_iommu_unmap(&dpu->dev, sprd_gem);
> + }
> +
> + if (unlikely(atomic_inc_not_zero(&logo2animation)) &&
> + dpu->ctx.logo_addr) {
> + DRM_INFO("free logo memory addr:0x%lx size:0x%lx\n",
> + dpu->ctx.logo_addr, dpu->ctx.logo_size);
> + free_reserved_area(phys_to_virt(dpu->ctx.logo_addr),
> + phys_to_virt(dpu->ctx.logo_addr + dpu->ctx.logo_size),
> + -1, "logo");
> + }
> +}
> +
> +static int sprd_plane_atomic_check(struct drm_plane *plane,
> + struct drm_plane_state *state)
> +{
> + DRM_DEBUG("%s()\n", __func__);
> +
> + return 0;
> +}
> +
> +static void sprd_plane_atomic_update(struct drm_plane *plane,
> + struct drm_plane_state *old_state)
> +{
> + struct drm_plane_state *state = plane->state;
> + struct drm_framebuffer *fb = plane->state->fb;
> + struct drm_gem_object *obj;
> + struct sprd_gem_obj *sprd_gem;
> + struct sprd_plane *p = to_sprd_plane(plane);
> + struct sprd_plane_state *s = to_sprd_plane_state(state);
> + struct sprd_dpu *dpu = crtc_to_dpu(plane->state->crtc);
> + struct sprd_dpu_layer *layer = &dpu->layers[p->index];
> + int i;
> +
> + if (plane->state->crtc->state->active_changed) {
> + DRM_DEBUG("resume or suspend, no need to update plane\n");
> + return;
> + }
> +
> + if (s->pallete_en) {
> + layer->index = p->index;
> + layer->dst_x = state->crtc_x;
> + layer->dst_y = state->crtc_y;
> + layer->dst_w = state->crtc_w;
> + layer->dst_h = state->crtc_h;
> + layer->alpha = s->alpha;
> + layer->blending = s->blend_mode;
> + layer->pallete_en = s->pallete_en;
> + layer->pallete_color = s->pallete_color;
> + dpu->pending_planes++;
> + DRM_DEBUG("%s() pallete_color = %u, index = %u\n",
> + __func__, layer->pallete_color, layer->index);
> + return;
> + }
> +
> + layer->index = p->index;
> + layer->src_x = state->src_x >> 16;
> + layer->src_y = state->src_y >> 16;
> + layer->src_w = state->src_w >> 16;
> + layer->src_h = state->src_h >> 16;
> + layer->dst_x = state->crtc_x;
> + layer->dst_y = state->crtc_y;
> + layer->dst_w = state->crtc_w;
> + layer->dst_h = state->crtc_h;
> + layer->rotation = state->rotation;
> + layer->planes = fb->format->num_planes;
> + layer->format = fb->format->format;
> + layer->alpha = s->alpha;
> + layer->blending = s->blend_mode;
> + layer->xfbc = fb->modifier;
> + layer->header_size_r = s->fbc_hsize_r;
> + layer->header_size_y = s->fbc_hsize_y;
> + layer->header_size_uv = s->fbc_hsize_uv;
> + layer->y2r_coef = s->y2r_coef;
> + layer->pallete_en = s->pallete_en;
> + layer->pallete_color = s->pallete_color;
> +
> + DRM_DEBUG("%s() alpha = %u, blending = %u, rotation = %u, y2r_coef = %u\n",
> + __func__, layer->alpha, layer->blending, layer->rotation, s->y2r_coef);
> +
> + DRM_DEBUG("%s() xfbc = %u, hsize_r = %u, hsize_y = %u, hsize_uv = %u\n",
> + __func__, layer->xfbc, layer->header_size_r,
> + layer->header_size_y, layer->header_size_uv);
> +
> + for (i = 0; i < layer->planes; i++) {
> + obj = drm_gem_fb_get_obj(fb, i);
> + sprd_gem = to_sprd_gem_obj(obj);
> + layer->addr[i] = sprd_gem->dma_addr + fb->offsets[i];
> + layer->pitch[i] = fb->pitches[i];
> + }
> +
> + dpu->pending_planes++;
> +}
> +
> +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> + struct drm_plane_state *old_state)
> +{
> + struct sprd_plane *p = to_sprd_plane(plane);
> +
> + /*
> + * NOTE:
> + * The dpu->core->flip() will disable all the planes each time.
> + * So there is no need to impliment the atomic_disable() function.
> + * But this function can not be removed, because it will change
> + * to call atomic_update() callback instead. Which will cause
> + * kernel panic in sprd_plane_atomic_update().
> + *
> + * We do nothing here but just print a debug log.
> + */
> + DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
> +}
> +
> +static void sprd_plane_reset(struct drm_plane *plane)
> +{
> + struct sprd_plane *p = to_sprd_plane(plane);
> + struct sprd_plane_state *s;
> +
> + DRM_INFO("%s()\n", __func__);
> +
> + if (plane->state) {
> + __drm_atomic_helper_plane_destroy_state(plane->state);
> +
> + s = to_sprd_plane_state(plane->state);
> + memset(s, 0, sizeof(*s));
> + } else {
> + s = kzalloc(sizeof(*s), GFP_KERNEL);
> + if (!s)
> + return;
> + plane->state = &s->state;
> + }
> +
> + s->state.plane = plane;
> + s->state.zpos = p->index;
> + s->alpha = 255;
> + s->blend_mode = DRM_MODE_BLEND_PIXEL_NONE;
> +}
> +
> +static struct drm_plane_state *
> +sprd_plane_atomic_duplicate_state(struct drm_plane *plane)
> +{
> + struct sprd_plane_state *s;
> + struct sprd_plane_state *old_state = to_sprd_plane_state(plane->state);
> +
> + DRM_DEBUG("%s()\n", __func__);
> +
> + s = kzalloc(sizeof(*s), GFP_KERNEL);
> + if (!s)
> + return NULL;
> +
> + __drm_atomic_helper_plane_duplicate_state(plane, &s->state);
> +
> + WARN_ON(s->state.plane != plane);
> +
> + s->alpha = old_state->alpha;
> + s->blend_mode = old_state->blend_mode;
> + s->fbc_hsize_r = old_state->fbc_hsize_r;
> + s->fbc_hsize_y = old_state->fbc_hsize_y;
> + s->fbc_hsize_uv = old_state->fbc_hsize_uv;
> + s->y2r_coef = old_state->y2r_coef;
> + s->pallete_en = old_state->pallete_en;
> + s->pallete_color = old_state->pallete_color;
> +
> + return &s->state;
> +}
> +
> +static void sprd_plane_atomic_destroy_state(struct drm_plane *plane,
> + struct drm_plane_state *state)
> +{
> + DRM_DEBUG("%s()\n", __func__);
> +
> + __drm_atomic_helper_plane_destroy_state(state);
> + kfree(to_sprd_plane_state(state));
> +}
> +
> +static int sprd_plane_atomic_set_property(struct drm_plane *plane,
> + struct drm_plane_state *state,
> + struct drm_property *property,
> + u64 val)
> +{
> + struct sprd_plane *p = to_sprd_plane(plane);
> + struct sprd_plane_state *s = to_sprd_plane_state(state);
> +
> + DRM_DEBUG("%s() name = %s, val = %llu\n",
> + __func__, property->name, val);
> +
> + if (property == p->alpha_property)
> + s->alpha = val;
> + else if (property == p->blend_mode_property)
> + s->blend_mode = val;
> + else if (property == p->fbc_hsize_r_property)
> + s->fbc_hsize_r = val;
> + else if (property == p->fbc_hsize_y_property)
> + s->fbc_hsize_y = val;
> + else if (property == p->fbc_hsize_uv_property)
> + s->fbc_hsize_uv = val;
> + else if (property == p->y2r_coef_property)
> + s->y2r_coef = val;
> + else if (property == p->pallete_en_property)
> + s->pallete_en = val;
> + else if (property == p->pallete_color_property)
> + s->pallete_color = val;
> + else {
> + DRM_ERROR("property %s is invalid\n", property->name);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int sprd_plane_atomic_get_property(struct drm_plane *plane,
> + const struct drm_plane_state *state,
> + struct drm_property *property,
> + u64 *val)
> +{
> + struct sprd_plane *p = to_sprd_plane(plane);
> + const struct sprd_plane_state *s = to_sprd_plane_state(state);
> +
> + DRM_DEBUG("%s() name = %s\n", __func__, property->name);
> +
> + if (property == p->alpha_property)
> + *val = s->alpha;
> + else if (property == p->blend_mode_property)
> + *val = s->blend_mode;
> + else if (property == p->fbc_hsize_r_property)
> + *val = s->fbc_hsize_r;
> + else if (property == p->fbc_hsize_y_property)
> + *val = s->fbc_hsize_y;
> + else if (property == p->fbc_hsize_uv_property)
> + *val = s->fbc_hsize_uv;
> + else if (property == p->y2r_coef_property)
> + *val = s->y2r_coef;
> + else if (property == p->pallete_en_property)
> + *val = s->pallete_en;
> + else if (property == p->pallete_color_property)
> + *val = s->pallete_color;
> + else {
> + DRM_ERROR("property %s is invalid\n", property->name);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> +{
> + struct drm_property *prop;
> + static const struct drm_prop_enum_list blend_mode_enum_list[] = {
> + { DRM_MODE_BLEND_PIXEL_NONE, "None" },
> + { DRM_MODE_BLEND_PREMULTI, "Pre-multiplied" },
> + { DRM_MODE_BLEND_COVERAGE, "Coverage" },
> + };
> +
> + /* create rotation property */
> + drm_plane_create_rotation_property(&p->plane,
> + DRM_MODE_ROTATE_0,
> + DRM_MODE_ROTATE_MASK |
> + DRM_MODE_REFLECT_MASK);
> +
> + /* create zpos property */
> + drm_plane_create_zpos_immutable_property(&p->plane, index);
> +
> + /* create layer alpha property */
> + prop = drm_property_create_range(p->plane.dev, 0, "alpha", 0, 255);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 255);
> + p->alpha_property = prop;
> +
> + /* create blend mode property */
> + prop = drm_property_create_enum(p->plane.dev, DRM_MODE_PROP_ENUM,
> + "pixel blend mode",
> + blend_mode_enum_list,
> + ARRAY_SIZE(blend_mode_enum_list));
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop,
> + DRM_MODE_BLEND_PIXEL_NONE);
> + p->blend_mode_property = prop;
> +
> + /* create fbc header size property */
> + prop = drm_property_create_range(p->plane.dev, 0,
> + "FBC header size RGB", 0, UINT_MAX);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 0);
> + p->fbc_hsize_r_property = prop;
> +
> + prop = drm_property_create_range(p->plane.dev, 0,
> + "FBC header size Y", 0, UINT_MAX);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 0);
> + p->fbc_hsize_y_property = prop;
> +
> + prop = drm_property_create_range(p->plane.dev, 0,
> + "FBC header size UV", 0, UINT_MAX);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 0);
> + p->fbc_hsize_uv_property = prop;
> +
> + /* create y2r coef property */
> + prop = drm_property_create_range(p->plane.dev, 0,
> + "YUV2RGB coef", 0, UINT_MAX);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 0);
> + p->y2r_coef_property = prop;
> +
> + /* create pallete enable property */
> + prop = drm_property_create_range(p->plane.dev, 0,
> + "pallete enable", 0, UINT_MAX);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 0);
> + p->pallete_en_property = prop;
> +
> + /* create pallete color property */
> + prop = drm_property_create_range(p->plane.dev, 0,
> + "pallete color", 0, UINT_MAX);
> + if (!prop)
> + return -ENOMEM;
> + drm_object_attach_property(&p->plane.base, prop, 0);
> + p->pallete_color_property = prop;
> +
> + return 0;
> +}
> +
> +static const struct drm_plane_helper_funcs sprd_plane_helper_funcs = {
> + .prepare_fb = sprd_plane_prepare_fb,
> + .cleanup_fb = sprd_plane_cleanup_fb,
> + .atomic_check = sprd_plane_atomic_check,
> + .atomic_update = sprd_plane_atomic_update,
> + .atomic_disable = sprd_plane_atomic_disable,
> +};
> +
> +static const struct drm_plane_funcs sprd_plane_funcs = {
> + .update_plane = drm_atomic_helper_update_plane,
> + .disable_plane = drm_atomic_helper_disable_plane,
> + .destroy = drm_plane_cleanup,
> + .reset = sprd_plane_reset,
> + .atomic_duplicate_state = sprd_plane_atomic_duplicate_state,
> + .atomic_destroy_state = sprd_plane_atomic_destroy_state,
> + .atomic_set_property = sprd_plane_atomic_set_property,
> + .atomic_get_property = sprd_plane_atomic_get_property,
> +};
> +
> +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
> + struct sprd_dpu *dpu)
> +{
> + struct drm_plane *primary = NULL;
> + struct sprd_plane *p = NULL;
> + struct dpu_capability cap = {};
> + int err, i;
> +
> + if (dpu->core && dpu->core->capability)
> + dpu->core->capability(&dpu->ctx, &cap);
> +
> + dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> + sizeof(struct sprd_dpu_layer), GFP_KERNEL);
> + if (!dpu->layers)
> + return ERR_PTR(-ENOMEM);
> +
> + for (i = 0; i < cap.max_layers; i++) {
> +
> + p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> + if (!p)
> + return ERR_PTR(-ENOMEM);
> +
> + err = drm_universal_plane_init(drm, &p->plane, 1,
> + &sprd_plane_funcs, cap.fmts_ptr,
> + cap.fmts_cnt, NULL,
> + DRM_PLANE_TYPE_PRIMARY, NULL);
> + if (err) {
> + DRM_ERROR("fail to init primary plane\n");
> + return ERR_PTR(err);
> + }
> +
> + drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
> +
> + sprd_plane_create_properties(p, i);
> +
> + p->index = i;
> + if (i == 0)
> + primary = &p->plane;
> + }
> +
> + if (p)
> + DRM_INFO("dpu plane init ok\n");
> +
> + return primary;
> +}
> +
> +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> + struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> +
> + DRM_INFO("%s() set mode: %s\n", __func__, dpu->mode->name);
> +
> + /*
> + * TODO:
> + * Currently, low simulator resolution only support
> + * DPI mode, support for EDPI in the future.
> + */
> + if (mode->type & DRM_MODE_TYPE_BUILTIN) {
> + dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> + return;
> + }
> +
> + if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> + (dpu->mode->vdisplay == dpu->mode->vtotal))
> + dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> + else
> + dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> +
> + if (dpu->core && dpu->core->modeset) {
> + if (crtc->state->mode_changed) {
> + struct drm_mode_modeinfo umode;
> +
> + drm_mode_convert_to_umode(&umode, mode);
> + dpu->core->modeset(&dpu->ctx, &umode);
> + }
> + }
> +}
> +
> +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> + const struct drm_display_mode *mode)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> + DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
> +
> + if (mode->type & DRM_MODE_TYPE_DEFAULT)
> + dpu->mode = (struct drm_display_mode *)mode;
> +
> + if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> + dpu->mode = (struct drm_display_mode *)mode;
> + drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
> + }
> +
> + if (mode->type & DRM_MODE_TYPE_BUILTIN)
> + dpu->mode = (struct drm_display_mode *)mode;
> +
> + return MODE_OK;
> +}
> +
> +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> + struct drm_crtc_state *old_state)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> + static bool is_enabled = true;
> +
> + DRM_INFO("%s()\n", __func__);
> +
> + /*
> + * add if condition to avoid resume dpu for SR feature.
> + */
> + if (crtc->state->mode_changed && !crtc->state->active_changed)
> + return;
> +
> + if (is_enabled)
> + is_enabled = false;
> + else
> + pm_runtime_get_sync(dpu->dev.parent);
> +
> + sprd_dpu_init(dpu);
> +
> + enable_irq(dpu->ctx.irq);
> +}
> +
> +static void sprd_crtc_wait_last_commit_complete(struct drm_crtc *crtc)
> +{
> + struct drm_crtc_commit *commit;
> + int ret, i = 0;
> +
> + spin_lock(&crtc->commit_lock);
> + list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
> + i++;
> + /* skip the first entry, that's the current commit */
> + if (i == 2)
> + break;
> + }
> + if (i == 2)
> + drm_crtc_commit_get(commit);
> + spin_unlock(&crtc->commit_lock);
> +
> + if (i != 2)
> + return;
> +
> + ret = wait_for_completion_interruptible_timeout(&commit->cleanup_done,
> + HZ);
> + if (ret == 0)
> + DRM_WARN("wait last commit completion timed out\n");
> +
> + drm_crtc_commit_put(commit);
> +}
> +
> +static void sprd_crtc_atomic_disable(struct drm_crtc *crtc,
> + struct drm_crtc_state *old_state)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> + struct drm_device *drm = dpu->crtc.dev;
> +
> + DRM_INFO("%s()\n", __func__);
> +
> + /* add if condition to avoid suspend dpu for SR feature */
> + if (crtc->state->mode_changed && !crtc->state->active_changed)
> + return;
> +
> + sprd_crtc_wait_last_commit_complete(crtc);
> +
> + disable_irq(dpu->ctx.irq);
> +
> + sprd_dpu_uninit(dpu);
> +
> + pm_runtime_put(dpu->dev.parent);
> +
> + spin_lock_irq(&drm->event_lock);
> + if (crtc->state->event) {
> + drm_crtc_send_vblank_event(crtc, crtc->state->event);
> + crtc->state->event = NULL;
> + }
> + spin_unlock_irq(&drm->event_lock);
> +}
> +
> +static int sprd_crtc_atomic_check(struct drm_crtc *crtc,
> + struct drm_crtc_state *state)
> +{
> + DRM_DEBUG("%s()\n", __func__);
> +
> + return 0;
> +}
> +
> +static void sprd_crtc_atomic_begin(struct drm_crtc *crtc,
> + struct drm_crtc_state *old_state)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> + DRM_DEBUG("%s()\n", __func__);
> +
> + down(&dpu->ctx.refresh_lock);
> +
> + memset(dpu->layers, 0, sizeof(*dpu->layers) * dpu->pending_planes);
> +
> + dpu->pending_planes = 0;
> +}
> +
> +static void sprd_crtc_atomic_flush(struct drm_crtc *crtc,
> + struct drm_crtc_state *old_state)
> +
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> + struct drm_device *drm = dpu->crtc.dev;
> +
> + DRM_DEBUG("%s()\n", __func__);
> +
> + if (dpu->core && dpu->core->flip &&
> + dpu->pending_planes && !dpu->ctx.disable_flip)
> + dpu->core->flip(&dpu->ctx, dpu->layers, dpu->pending_planes);
> +
> + up(&dpu->ctx.refresh_lock);
> +
> + spin_lock_irq(&drm->event_lock);
> + if (crtc->state->event) {
> + drm_crtc_send_vblank_event(crtc, crtc->state->event);
> + crtc->state->event = NULL;
> + }
> + spin_unlock_irq(&drm->event_lock);
> +}
> +
> +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> + DRM_DEBUG("%s()\n", __func__);
> +
> + if (dpu->core && dpu->core->enable_vsync)
> + dpu->core->enable_vsync(&dpu->ctx);
> +
> + return 0;
> +}
> +
> +static void sprd_crtc_disable_vblank(struct drm_crtc *crtc)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> + DRM_DEBUG("%s()\n", __func__);
> +
> + if (dpu->core && dpu->core->disable_vsync)
> + dpu->core->disable_vsync(&dpu->ctx);
> +}
> +
> +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> +{
> + struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> + struct drm_device *drm = dpu->crtc.dev;
> + struct drm_property *prop;
> + struct drm_property_blob *blob;
> + size_t blob_size;
> +
> + blob_size = strlen(dpu->ctx.version) + 1;
> +
> + blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> + dpu->ctx.version);
> + if (IS_ERR(blob)) {
> + DRM_ERROR("drm_property_create_blob dpu version failed\n");
> + return PTR_ERR(blob);
> + }
> +
> + /* create dpu version property */
> + prop = drm_property_create(drm,
> + DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> + "dpu version", 0);
> + if (!prop) {
> + DRM_ERROR("drm_property_create dpu version failed\n");
> + return -ENOMEM;
> + }
> + drm_object_attach_property(&crtc->base, prop, blob->base.id);
> +
> + /* create corner size property */
> + prop = drm_property_create(drm,
> + DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_RANGE,
> + "corner size", 0);
> + if (!prop) {
> + DRM_ERROR("drm_property_create corner size failed\n");
> + return -ENOMEM;
> + }
> + drm_object_attach_property(&crtc->base, prop, dpu->ctx.corner_size);
> +
> + return 0;
> +}
> +
> +static const struct drm_crtc_helper_funcs sprd_crtc_helper_funcs = {
> + .mode_set_nofb = sprd_crtc_mode_set_nofb,
> + .mode_valid = sprd_crtc_mode_valid,
> + .atomic_check = sprd_crtc_atomic_check,
> + .atomic_begin = sprd_crtc_atomic_begin,
> + .atomic_flush = sprd_crtc_atomic_flush,
> + .atomic_enable = sprd_crtc_atomic_enable,
> + .atomic_disable = sprd_crtc_atomic_disable,
> +};
> +
> +static const struct drm_crtc_funcs sprd_crtc_funcs = {
> + .destroy = drm_crtc_cleanup,
> + .set_config = drm_atomic_helper_set_config,
> + .page_flip = drm_atomic_helper_page_flip,
> + .reset = drm_atomic_helper_crtc_reset,
> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> + .enable_vblank = sprd_crtc_enable_vblank,
> + .disable_vblank = sprd_crtc_disable_vblank,
> +};
> +
> +static int sprd_crtc_init(struct drm_device *drm, struct drm_crtc *crtc,
> + struct drm_plane *primary)
> +{
> + struct device_node *port;
> + int err;
> +
> + /*
> + * set crtc port so that drm_of_find_possible_crtcs call works
> + */
> + port = of_parse_phandle(drm->dev->of_node, "ports", 0);
> + if (!port) {
> + DRM_ERROR("find 'ports' phandle of %s failed\n",
> + drm->dev->of_node->full_name);
> + return -EINVAL;
> + }
> + of_node_put(port);
> + crtc->port = port;
> +
> + err = drm_crtc_init_with_planes(drm, crtc, primary, NULL,
> + &sprd_crtc_funcs, NULL);
> + if (err) {
> + DRM_ERROR("failed to init crtc.\n");
> + return err;
> + }
> +
> + drm_mode_crtc_set_gamma_size(crtc, 256);
> +
> + drm_crtc_helper_add(crtc, &sprd_crtc_helper_funcs);
> +
> + sprd_crtc_create_properties(crtc);
> +
> + DRM_INFO("%s() ok\n", __func__);
> + return 0;
> +}
> +
> +int sprd_dpu_run(struct sprd_dpu *dpu)
> +{
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + down(&ctx->refresh_lock);
> +
> + if (!ctx->is_inited) {
> + DRM_ERROR("dpu is not initialized\n");
> + up(&ctx->refresh_lock);
> + return -EINVAL;
> + }
> +
> + if (!ctx->is_stopped) {
> + up(&ctx->refresh_lock);
> + return 0;
> + }
> +
> + if (dpu->core && dpu->core->run)
> + dpu->core->run(ctx);
> +
> + up(&ctx->refresh_lock);
> +
> + drm_crtc_vblank_on(&dpu->crtc);
> +
> + return 0;
> +}
> +
> +int sprd_dpu_stop(struct sprd_dpu *dpu)
> +{
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + down(&ctx->refresh_lock);
> +
> + if (!ctx->is_inited) {
> + DRM_ERROR("dpu is not initialized\n");
> + up(&ctx->refresh_lock);
> + return -EINVAL;
> + }
> +
> + if (ctx->is_stopped) {
> + up(&ctx->refresh_lock);
> + return 0;
> + }
> +
> + if (dpu->core && dpu->core->stop)
> + dpu->core->stop(ctx);
> +
> + up(&ctx->refresh_lock);
> +
> + drm_crtc_handle_vblank(&dpu->crtc);
> + drm_crtc_vblank_off(&dpu->crtc);
> +
> + return 0;
> +}
> +
> +static int sprd_dpu_init(struct sprd_dpu *dpu)
> +{
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + down(&ctx->refresh_lock);
> +
> + if (dpu->ctx.is_inited) {
> + up(&ctx->refresh_lock);
> + return 0;
> + }
> +
> + if (dpu->glb && dpu->glb->power)
> + dpu->glb->power(ctx, true);
> + if (dpu->glb && dpu->glb->enable)
> + dpu->glb->enable(ctx);
> +
> + if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
> + dpu->glb->reset(ctx);
> +
> + if (dpu->clk && dpu->clk->init)
> + dpu->clk->init(ctx);
> + if (dpu->clk && dpu->clk->enable)
> + dpu->clk->enable(ctx);
> +
> + if (dpu->core && dpu->core->init)
> + dpu->core->init(ctx);
> + if (dpu->core && dpu->core->ifconfig)
> + dpu->core->ifconfig(ctx);
> +
> + ctx->is_inited = true;
> +
> + up(&ctx->refresh_lock);
> +
> + return 0;
> +}
> +
> +static int sprd_dpu_uninit(struct sprd_dpu *dpu)
> +{
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + down(&ctx->refresh_lock);
> +
> + if (!dpu->ctx.is_inited) {
> + up(&ctx->refresh_lock);
> + return 0;
> + }
> +
> + if (dpu->core && dpu->core->uninit)
> + dpu->core->uninit(ctx);
> + if (dpu->clk && dpu->clk->disable)
> + dpu->clk->disable(ctx);
> + if (dpu->glb && dpu->glb->disable)
> + dpu->glb->disable(ctx);
> + if (dpu->glb && dpu->glb->power)
> + dpu->glb->power(ctx, false);
> +
> + ctx->is_inited = false;
> +
> + up(&ctx->refresh_lock);
> +
> + return 0;
> +}
> +
> +static irqreturn_t sprd_dpu_isr(int irq, void *data)
> +{
> + struct sprd_dpu *dpu = data;
> + struct dpu_context *ctx = &dpu->ctx;
> + u32 int_mask = 0;
> +
> + if (dpu->core && dpu->core->isr)
> + int_mask = dpu->core->isr(ctx);
> +
> + if (int_mask & DISPC_INT_TE_MASK) {
> + if (ctx->te_check_en) {
> + ctx->evt_te = true;
> + wake_up_interruptible_all(&ctx->te_wq);
> + }
> + }
> +
> + if (int_mask & DISPC_INT_ERR_MASK)
> + DRM_WARN("Warning: dpu underflow!\n");
> +
> + if ((int_mask & DISPC_INT_DPI_VSYNC_MASK) && ctx->is_inited)
> + drm_crtc_handle_vblank(&dpu->crtc);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int sprd_dpu_irq_request(struct sprd_dpu *dpu)
> +{
> + int err;
> + int irq_num;
> +
> + irq_num = irq_of_parse_and_map(dpu->dev.of_node, 0);
> + if (!irq_num) {
> + DRM_ERROR("error: dpu parse irq num failed\n");
> + return -EINVAL;
> + }
> + DRM_INFO("dpu irq_num = %d\n", irq_num);
> +
> + irq_set_status_flags(irq_num, IRQ_NOAUTOEN);
> + err = devm_request_irq(&dpu->dev, irq_num, sprd_dpu_isr,
> + 0, "DISPC", dpu);
> + if (err) {
> + DRM_ERROR("error: dpu request irq failed\n");
> + return -EINVAL;
> + }
> + dpu->ctx.irq = irq_num;
> + dpu->ctx.dpu_isr = sprd_dpu_isr;
> +
> + return 0;
> +}
> +
> +static int sprd_dpu_bind(struct device *dev, struct device *master, void *data)
> +{
> + struct drm_device *drm = data;
> + struct sprd_drm *sprd = drm->dev_private;
> + struct sprd_dpu *dpu = dev_get_drvdata(dev);
> + struct drm_plane *plane;
> + int err;
> +
> + DRM_INFO("%s()\n", __func__);
> +
> + plane = sprd_plane_init(drm, dpu);
> + if (IS_ERR_OR_NULL(plane)) {
> + err = PTR_ERR(plane);
> + return err;
> + }
> +
> + err = sprd_crtc_init(drm, &dpu->crtc, plane);
> + if (err)
> + return err;
> +
> + sprd_dpu_irq_request(dpu);
> +
> + sprd->dpu_dev = dev;
> +
> + return 0;
> +}
> +
> +static void sprd_dpu_unbind(struct device *dev, struct device *master,
> + void *data)
> +{
> + struct sprd_dpu *dpu = dev_get_drvdata(dev);
> +
> + DRM_INFO("%s()\n", __func__);
> +
> + drm_crtc_cleanup(&dpu->crtc);
> +}
> +
> +static const struct component_ops dpu_component_ops = {
> + .bind = sprd_dpu_bind,
> + .unbind = sprd_dpu_unbind,
> +};
> +
> +static int sprd_dpu_context_init(struct sprd_dpu *dpu,
> + struct device_node *np)
> +{
> + u32 temp;
> + struct resource r;
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + if (dpu->core && dpu->core->parse_dt)
> + dpu->core->parse_dt(&dpu->ctx, np);
> + if (dpu->clk && dpu->clk->parse_dt)
> + dpu->clk->parse_dt(&dpu->ctx, np);
> + if (dpu->glb && dpu->glb->parse_dt)
> + dpu->glb->parse_dt(&dpu->ctx, np);
> +
> + if (!of_property_read_u32(np, "sprd,dev-id", &temp))
> + ctx->id = temp;
> +
> + if (of_address_to_resource(np, 0, &r)) {
> + DRM_ERROR("parse dt base address failed\n");
> + return -ENODEV;
> + }
> + ctx->base = (unsigned long)ioremap_nocache(r.start,
> + resource_size(&r));
> + if (ctx->base == 0) {
> + DRM_ERROR("ioremap base address failed\n");
> + return -EFAULT;
> + }
> +
> + of_get_logo_memory_info(dpu, np);
> +
> + sema_init(&ctx->refresh_lock, 1);
> +
> + return 0;
> +}
> +
> +static int sprd_dpu_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct sprd_dpu *dpu;
> + int ret;
> +
> + dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
> + if (!dpu)
> + return -ENOMEM;
> +
> + ret = sprd_dpu_context_init(dpu, np);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, dpu);
> + pm_runtime_set_active(&pdev->dev);
> + pm_runtime_get_noresume(&pdev->dev);
> + pm_runtime_enable(&pdev->dev);
> +
> + return component_add(&pdev->dev, &dpu_component_ops);
> +}
> +
> +static int sprd_dpu_remove(struct platform_device *pdev)
> +{
> + component_del(&pdev->dev, &dpu_component_ops);
> + return 0;
> +}
> +
> +static const struct of_device_id dpu_match_table[] = {
> + { .compatible = "sprd,display-processor",},
> + {},
> +};
> +
> +static struct platform_driver sprd_dpu_driver = {
> + .probe = sprd_dpu_probe,
> + .remove = sprd_dpu_remove,
> + .driver = {
> + .name = "sprd-dpu-drv",
> + .of_match_table = dpu_match_table,
> + },
> +};
> +module_platform_driver(sprd_dpu_driver);
> +
> +MODULE_AUTHOR("Leon He <[email protected]>");
> +MODULE_AUTHOR("Kevin Tang <[email protected]>");
> +MODULE_DESCRIPTION("Unisoc Display Controller Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/sprd/sprd_dpu.h b/drivers/gpu/drm/sprd/sprd_dpu.h
> new file mode 100644
> index 0000000..998ebc7
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_dpu.h
> @@ -0,0 +1,217 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef __SPRD_DPU_H__
> +#define __SPRD_DPU_H__
> +
> +#include <linux/bug.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/string.h>
> +#include <video/videomode.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_vblank.h>
> +#include <uapi/drm/drm_mode.h>
> +#include "disp_lib.h"
> +
> +#define DISPC_INT_DONE_MASK BIT(0)
> +#define DISPC_INT_TE_MASK BIT(1)
> +#define DISPC_INT_ERR_MASK BIT(2)
> +#define DISPC_INT_EDPI_TE_MASK BIT(3)
> +#define DISPC_INT_UPDATE_DONE_MASK BIT(4)
> +#define DISPC_INT_DPI_VSYNC_MASK BIT(5)
> +#define DISPC_INT_WB_DONE_MASK BIT(6)
> +#define DISPC_INT_WB_FAIL_MASK BIT(7)
> +
> +/* NOTE: this mask is not a realy dpu interrupt mask */
> +#define DISPC_INT_FENCE_SIGNAL_REQUEST BIT(31)
> +
> +enum {
> + SPRD_DISPC_IF_DBI = 0,
> + SPRD_DISPC_IF_DPI,
> + SPRD_DISPC_IF_EDPI,
> + SPRD_DISPC_IF_LIMIT
> +};
> +
> +enum {
> + SPRD_IMG_DATA_ENDIAN_B0B1B2B3 = 0,
> + SPRD_IMG_DATA_ENDIAN_B3B2B1B0,
> + SPRD_IMG_DATA_ENDIAN_B2B3B0B1,
> + SPRD_IMG_DATA_ENDIAN_B1B0B3B2,
> + SPRD_IMG_DATA_ENDIAN_LIMIT
> +};
> +
> +enum {
> + DISPC_CLK_ID_CORE = 0,
> + DISPC_CLK_ID_DBI,
> + DISPC_CLK_ID_DPI,
> + DISPC_CLK_ID_MAX
> +};
> +
> +enum {
> + ENHANCE_CFG_ID_ENABLE,
> + ENHANCE_CFG_ID_DISABLE,
> + ENHANCE_CFG_ID_SCL,
> + ENHANCE_CFG_ID_EPF,
> + ENHANCE_CFG_ID_HSV,
> + ENHANCE_CFG_ID_CM,
> + ENHANCE_CFG_ID_SLP,
> + ENHANCE_CFG_ID_GAMMA,
> + ENHANCE_CFG_ID_LTM,
> + ENHANCE_CFG_ID_CABC,
> + ENHANCE_CFG_ID_SLP_LUT,
> + ENHANCE_CFG_ID_LUT3D,
> + ENHANCE_CFG_ID_MAX
> +};
> +
> +struct sprd_dpu_layer {
> + u8 index;
> + u8 planes;
> + u32 addr[4];
> + u32 pitch[4];
> + s16 src_x;
> + s16 src_y;
> + s16 src_w;
> + s16 src_h;
> + s16 dst_x;
> + s16 dst_y;
> + u16 dst_w;
> + u16 dst_h;
> + u32 format;
> + u32 alpha;
> + u32 blending;
> + u32 rotation;
> + u32 xfbc;
> + u32 height;
> + u32 header_size_r;
> + u32 header_size_y;
> + u32 header_size_uv;
> + u32 y2r_coef;
> + u8 pallete_en;
> + u32 pallete_color;
> +};
> +
> +struct dpu_capability {
> + u32 max_layers;
> + const u32 *fmts_ptr;
> + u32 fmts_cnt;
> +};
> +
> +struct dpu_context;
> +
> +struct dpu_core_ops {
> + int (*parse_dt)(struct dpu_context *ctx,
> + struct device_node *np);
> + u32 (*version)(struct dpu_context *ctx);
> + int (*init)(struct dpu_context *ctx);
> + void (*uninit)(struct dpu_context *ctx);
> + void (*run)(struct dpu_context *ctx);
> + void (*stop)(struct dpu_context *ctx);
> + void (*disable_vsync)(struct dpu_context *ctx);
> + void (*enable_vsync)(struct dpu_context *ctx);
> + u32 (*isr)(struct dpu_context *ctx);
> + void (*ifconfig)(struct dpu_context *ctx);
> + void (*write_back)(struct dpu_context *ctx, u8 count, bool debug);
> + void (*flip)(struct dpu_context *ctx,
> + struct sprd_dpu_layer layers[], u8 count);
> + int (*capability)(struct dpu_context *ctx,
> + struct dpu_capability *cap);
> + void (*bg_color)(struct dpu_context *ctx, u32 color);
> + void (*enhance_set)(struct dpu_context *ctx, u32 id, void *param);
> + void (*enhance_get)(struct dpu_context *ctx, u32 id, void *param);
> + int (*modeset)(struct dpu_context *ctx,
> + struct drm_mode_modeinfo *mode);
> + bool (*check_raw_int)(struct dpu_context *ctx, u32 mask);
> +};
> +
> +struct dpu_clk_ops {
> + int (*parse_dt)(struct dpu_context *ctx,
> + struct device_node *np);
> + int (*init)(struct dpu_context *ctx);
> + int (*uinit)(struct dpu_context *ctx);
> + int (*enable)(struct dpu_context *ctx);
> + int (*disable)(struct dpu_context *ctx);
> + int (*update)(struct dpu_context *ctx, int clk_id, int val);
> +};
> +
> +struct dpu_glb_ops {
> + int (*parse_dt)(struct dpu_context *ctx,
> + struct device_node *np);
> + void (*enable)(struct dpu_context *ctx);
> + void (*disable)(struct dpu_context *ctx);
> + void (*reset)(struct dpu_context *ctx);
> + void (*power)(struct dpu_context *ctx, int enable);
> +};
> +
> +struct dpu_context {
> + unsigned long base;
> + u32 base_offset[2];
> + const char *version;
> + u32 corner_size;
> + int irq;
> + u8 if_type;
> + u8 id;
> + bool is_inited;
> + bool is_stopped;
> + bool disable_flip;
> + struct videomode vm;
> + struct semaphore refresh_lock;
> + struct work_struct wb_work;
> + struct tasklet_struct dvfs_task;
> + u32 wb_addr_p;
> + irqreturn_t (*dpu_isr)(int irq, void *data);
> + wait_queue_head_t te_wq;
> + bool te_check_en;
> + bool evt_te;
> + unsigned long logo_addr;
> + unsigned long logo_size;
> + struct work_struct cabc_work;
> + struct work_struct cabc_bl_update;
> +};
> +
> +struct sprd_dpu {
> + struct device dev;
> + struct drm_crtc crtc;
> + struct dpu_context ctx;
> + struct dpu_core_ops *core;
> + struct dpu_clk_ops *clk;
> + struct dpu_glb_ops *glb;
> + struct drm_display_mode *mode;
> + struct sprd_dpu_layer *layers;
> + u8 pending_planes;
> +};
> +
> +extern struct list_head dpu_core_head;
> +extern struct list_head dpu_clk_head;
> +extern struct list_head dpu_glb_head;
> +
> +static inline struct sprd_dpu *crtc_to_dpu(struct drm_crtc *crtc)
> +{
> + return crtc ? container_of(crtc, struct sprd_dpu, crtc) : NULL;
> +}
> +
> +#define dpu_core_ops_register(entry) \
> + disp_ops_register(entry, &dpu_core_head)
> +#define dpu_clk_ops_register(entry) \
> + disp_ops_register(entry, &dpu_clk_head)
> +#define dpu_glb_ops_register(entry) \
> + disp_ops_register(entry, &dpu_glb_head)
> +
> +#define dpu_core_ops_attach(str) \
> + disp_ops_attach(str, &dpu_core_head)
> +#define dpu_clk_ops_attach(str) \
> + disp_ops_attach(str, &dpu_clk_head)
> +#define dpu_glb_ops_attach(str) \
> + disp_ops_attach(str, &dpu_glb_head)
> +
> +int sprd_dpu_run(struct sprd_dpu *dpu);
> +int sprd_dpu_stop(struct sprd_dpu *dpu);
> +
> +#endif
>

--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Maxfeldstr. 5, 90409 Nürnberg, Germany
(HRB 36809, AG Nürnberg)
Geschäftsführer: Felix Imendörffer


Attachments:
signature.asc (499.00 B)
OpenPGP digital signature

2019-12-10 12:48:34

by Thomas Zimmermann

[permalink] [raw]
Subject: Re: [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master

Hi

Am 10.12.19 um 13:38 schrieb tang pengchuan:
> Hi
>
> Thomas Zimmermann <[email protected] <mailto:[email protected]>>
> 2019年12月10日周二 下午6:33写道:
>
> Hi
>
> Am 10.12.19 um 09:36 schrieb Kevin Tang:
> > From: Kevin Tang <[email protected]
> <mailto:[email protected]>>
> >
> > Adds drm support for the Unisoc's display subsystem.
> >
> > This is drm device and gem driver. This driver provides support
> for the
> > Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
> >
> > Cc: Orson Zhai <[email protected] <mailto:[email protected]>>
> > Cc: Baolin Wang <[email protected]
> <mailto:[email protected]>>
> > Cc: Chunyan Zhang <[email protected] <mailto:[email protected]>>
> > Signed-off-by: Kevin Tang <[email protected]
> <mailto:[email protected]>>
> > ---
> >  drivers/gpu/drm/Kconfig         |   2 +
> >  drivers/gpu/drm/Makefile        |   1 +
> >  drivers/gpu/drm/sprd/Kconfig    |  14 ++
> >  drivers/gpu/drm/sprd/Makefile   |   8 ++
> >  drivers/gpu/drm/sprd/sprd_drm.c | 287
> ++++++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/sprd/sprd_drm.h |  19 +++
> >  drivers/gpu/drm/sprd/sprd_gem.c | 178 +++++++++++++++++++++++++
> >  drivers/gpu/drm/sprd/sprd_gem.h |  30 +++++
>
> The GEM implementation looks like DRM's CMA helpers. Can you not use CMA
> helpers instead?
>
> Ok, i will remove cma keywords from the GEM implementatio.

I'm not quite sure what you mean. Why can you not use the existing CMA
helpers instead of writing your own?

Best regards
Thomas

>
>
> >  8 files changed, 539 insertions(+)
> >  create mode 100644 drivers/gpu/drm/sprd/Kconfig
> >  create mode 100644 drivers/gpu/drm/sprd/Makefile
> >  create mode 100644 drivers/gpu/drm/sprd/sprd_drm.c
> >  create mode 100644 drivers/gpu/drm/sprd/sprd_drm.h
> >  create mode 100644 drivers/gpu/drm/sprd/sprd_gem.c
> >  create mode 100644 drivers/gpu/drm/sprd/sprd_gem.h
> >
> > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> > index bfdadc3..cead12c 100644
> > --- a/drivers/gpu/drm/Kconfig
> > +++ b/drivers/gpu/drm/Kconfig
> > @@ -387,6 +387,8 @@ source "drivers/gpu/drm/aspeed/Kconfig"
> > 
> >  source "drivers/gpu/drm/mcde/Kconfig"
> > 
> > +source "drivers/gpu/drm/sprd/Kconfig"
> > +
> >  # Keep legacy drivers last
> > 
> >  menuconfig DRM_LEGACY
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 9f1c7c4..85ca211 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -122,3 +122,4 @@ obj-$(CONFIG_DRM_LIMA)  += lima/
> >  obj-$(CONFIG_DRM_PANFROST) += panfrost/
> >  obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
> >  obj-$(CONFIG_DRM_MCDE) += mcde/
> > +obj-$(CONFIG_DRM_SPRD) += sprd/
> > diff --git a/drivers/gpu/drm/sprd/Kconfig
> b/drivers/gpu/drm/sprd/Kconfig
> > new file mode 100644
> > index 0000000..79f286b
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/Kconfig
> > @@ -0,0 +1,14 @@
> > +config DRM_SPRD
> > +     tristate "DRM Support for Unisoc SoCs Platform"
> > +     depends on ARCH_SPRD
> > +     depends on DRM && OF
> > +     select DRM_KMS_HELPER
> > +     select DRM_GEM_CMA_HELPER
> > +     select DRM_KMS_CMA_HELPER
> > +     select DRM_MIPI_DSI
> > +     select DRM_PANEL
> > +     select VIDEOMODE_HELPERS
> > +     select BACKLIGHT_CLASS_DEVICE
> > +     help
> > +       Choose this option if you have a Unisoc chipsets.
> > +       If M is selected the module will be called sprd-drm.
> > \ No newline at end of file
> > diff --git a/drivers/gpu/drm/sprd/Makefile
> b/drivers/gpu/drm/sprd/Makefile
> > new file mode 100644
> > index 0000000..df0b316
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/Makefile
> > @@ -0,0 +1,8 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +ccflags-y += -Iinclude/drm
> > +
> > +subdir-ccflags-y += -I$(src)
> > +
> > +obj-y := sprd_drm.o \
> > +     sprd_gem.o
> > \ No newline at end of file
> > diff --git a/drivers/gpu/drm/sprd/sprd_drm.c
> b/drivers/gpu/drm/sprd/sprd_drm.c
> > new file mode 100644
> > index 0000000..ec16fee
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_drm.c
> > @@ -0,0 +1,287 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2019 Unisoc Inc.
> > + */
> > +
> > +#include <linux/component.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/of_platform.h>
> > +
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_drv.h>
> > +#include <drm/drm_gem_cma_helper.h>
> > +#include <drm/drm_gem_framebuffer_helper.h>
> > +#include <drm/drm_probe_helper.h>
> > +#include <drm/drm_vblank.h>
> > +
> > +#include "sprd_drm.h"
> > +#include "sprd_gem.h"
> > +
> > +#define DRIVER_NAME  "sprd"
> > +#define DRIVER_DESC  "Spreadtrum SoCs' DRM Driver"
> > +#define DRIVER_DATE  "20180501"
> > +#define DRIVER_MAJOR 1
> > +#define DRIVER_MINOR 0
> > +
> > +static const struct drm_mode_config_helper_funcs
> sprd_drm_mode_config_helper = {
> > +     .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
> > +};
> > +
> > +static const struct drm_mode_config_funcs
> sprd_drm_mode_config_funcs = {
> > +     .fb_create = drm_gem_fb_create,
> > +     .atomic_check = drm_atomic_helper_check,
> > +     .atomic_commit = drm_atomic_helper_commit,
> > +};
> > +
> > +static void sprd_drm_mode_config_init(struct drm_device *drm)
> > +{
> > +     drm_mode_config_init(drm);
> > +
> > +     drm->mode_config.min_width = 0;
> > +     drm->mode_config.min_height = 0;
> > +     drm->mode_config.max_width = 8192;
> > +     drm->mode_config.max_height = 8192;
> > +     drm->mode_config.allow_fb_modifiers = true;
> > +
> > +     drm->mode_config.funcs = &sprd_drm_mode_config_funcs;
> > +     drm->mode_config.helper_private = &sprd_drm_mode_config_helper;
> > +}
> > +
> > +static const struct file_operations sprd_drm_fops = {
> > +     .owner          = THIS_MODULE,
> > +     .open           = drm_open,
> > +     .release        = drm_release,
> > +     .unlocked_ioctl = drm_ioctl,
> > +     .compat_ioctl   = drm_compat_ioctl,
> > +     .poll           = drm_poll,
> > +     .read           = drm_read,
> > +     .llseek         = no_llseek,
> > +     .mmap           = sprd_gem_cma_mmap,
> > +};
> > +
> > +static struct drm_driver sprd_drm_drv = {
> > +     .driver_features        = DRIVER_GEM | DRIVER_MODESET |
> > +                               DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
> > +     .fops                   = &sprd_drm_fops,
> > +
> > +     .gem_vm_ops             = &drm_gem_cma_vm_ops,
> > +     .gem_free_object_unlocked       = sprd_gem_free_object,
> > +     .dumb_create            = sprd_gem_cma_dumb_create,
> > +
> > +     .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
> > +     .gem_prime_import       = drm_gem_prime_import,
> > +     .gem_prime_import_sg_table = sprd_gem_prime_import_sg_table,
> > +
> > +     .name                   = DRIVER_NAME,
> > +     .desc                   = DRIVER_DESC,
> > +     .date                   = DRIVER_DATE,
> > +     .major                  = DRIVER_MAJOR,
> > +     .minor                  = DRIVER_MINOR,
> > +};
> > +
> > +static int sprd_drm_bind(struct device *dev)
> > +{
> > +     struct drm_device *drm;
> > +     struct sprd_drm *sprd;
> > +     int err;
> > +
> > +     drm = drm_dev_alloc(&sprd_drm_drv, dev);
> > +     if (IS_ERR(drm))
> > +             return PTR_ERR(drm);
> > +
> > +     dev_set_drvdata(dev, drm);
> > +
> > +     sprd = devm_kzalloc(drm->dev, sizeof(*sprd), GFP_KERNEL);
> > +     if (!sprd) {
> > +             err = -ENOMEM;
> > +             goto err_free_drm;
> > +     }
> > +     drm->dev_private = sprd;
> > +
> > +     sprd_drm_mode_config_init(drm);
> > +
> > +     /* bind and init sub drivers */
> > +     err = component_bind_all(drm->dev, drm);
> > +     if (err) {
> > +             DRM_ERROR("failed to bind all component.\n");
> > +             goto err_dc_cleanup;
> > +     }
> > +
> > +     /* vblank init */
> > +     err = drm_vblank_init(drm, drm->mode_config.num_crtc);
> > +     if (err) {
> > +             DRM_ERROR("failed to initialize vblank.\n");
> > +             goto err_unbind_all;
> > +     }
> > +     /* with irq_enabled = true, we can use the vblank feature. */
> > +     drm->irq_enabled = true;
> > +
> > +     /* reset all the states of crtc/plane/encoder/connector */
> > +     drm_mode_config_reset(drm);
> > +
> > +     /* init kms poll for handling hpd */
> > +     drm_kms_helper_poll_init(drm);
> > +
> > +     err = drm_dev_register(drm, 0);
> > +     if (err < 0)
> > +             goto err_kms_helper_poll_fini;
> > +
> > +     return 0;
> > +
> > +err_kms_helper_poll_fini:
> > +     drm_kms_helper_poll_fini(drm);
> > +err_unbind_all:
> > +     component_unbind_all(drm->dev, drm);
> > +err_dc_cleanup:
> > +     drm_mode_config_cleanup(drm);
> > +err_free_drm:
> > +     drm_dev_put(drm);
> > +     return err;
> > +}
> > +
> > +static void sprd_drm_unbind(struct device *dev)
> > +{
> > +     drm_put_dev(dev_get_drvdata(dev));
> > +}
> > +
> > +static const struct component_master_ops sprd_drm_component_ops = {
> > +     .bind = sprd_drm_bind,
> > +     .unbind = sprd_drm_unbind,
> > +};
> > +
> > +static int compare_of(struct device *dev, void *data)
> > +{
> > +     struct device_node *np = data;
> > +
> > +     DRM_DEBUG("compare %s\n", np->full_name);
> > +
> > +     return dev->of_node == np;
> > +}
> > +
> > +static int sprd_drm_component_probe(struct device *dev,
> > +                        const struct component_master_ops *m_ops)
> > +{
> > +     struct device_node *ep, *port, *remote;
> > +     struct component_match *match = NULL;
> > +     int i;
> > +
> > +     if (!dev->of_node)
> > +             return -EINVAL;
> > +
> > +     /*
> > +      * Bind the crtc's ports first, so that
> drm_of_find_possible_crtcs()
> > +      * called from encoder's .bind callbacks works as expected
> > +      */
> > +     for (i = 0; ; i++) {
> > +             port = of_parse_phandle(dev->of_node, "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;
> > +     }
> > +
> > +     if (!match) {
> > +             dev_err(dev, "no available port\n");
> > +             return -ENODEV;
> > +     }
> > +
> > +     /*
> > +      * For bound crtcs, bind the encoders attached to their
> remote endpoint
> > +      */
> > +     for (i = 0; ; i++) {
> > +             port = of_parse_phandle(dev->of_node, "ports", i);
> > +             if (!port)
> > +                     break;
> > +
> > +             if (!of_device_is_available(port->parent)) {
> > +                     of_node_put(port);
> > +                     continue;
> > +             }
> > +
> > +             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);
> > +             }
> > +             of_node_put(port);
> > +     }
> > +
> > +     return component_master_add_with_match(dev, m_ops, match);
> > +}
> > +
> > +static int sprd_drm_probe(struct platform_device *pdev)
> > +{
> > +     int ret;
> > +
> > +     ret = dma_set_mask_and_coherent(&pdev->dev, ~0);
> > +     if (ret)
> > +             DRM_ERROR("dma_set_mask_and_coherent failed (%d)\n",
> ret);
> > +
> > +     return sprd_drm_component_probe(&pdev->dev,
> &sprd_drm_component_ops);
> > +}
> > +
> > +static int sprd_drm_remove(struct platform_device *pdev)
> > +{
> > +     component_master_del(&pdev->dev, &sprd_drm_component_ops);
> > +     return 0;
> > +}
> > +
> > +static void sprd_drm_shutdown(struct platform_device *pdev)
> > +{
> > +     struct drm_device *drm = platform_get_drvdata(pdev);
> > +
> > +     if (!drm) {
> > +             DRM_WARN("drm device is not available, no shutdown\n");
> > +             return;
> > +     }
> > +
> > +     drm_atomic_helper_shutdown(drm);
> > +}
> > +
> > +static const struct of_device_id sprd_drm_match_table[] = {
> > +     { .compatible = "sprd,display-subsystem",},
> > +     {},
> > +};
> > +MODULE_DEVICE_TABLE(of, sprd_drm_match_table);
> > +
> > +static struct platform_driver sprd_drm_driver = {
> > +     .probe = sprd_drm_probe,
> > +     .remove = sprd_drm_remove,
> > +     .shutdown = sprd_drm_shutdown,
> > +     .driver = {
> > +             .name = "sprd-drm-drv",
> > +             .of_match_table = sprd_drm_match_table,
> > +     },
> > +};
> > +
> > +module_platform_driver(sprd_drm_driver);
> > +
> > +MODULE_AUTHOR("Leon He <[email protected]
> <mailto:[email protected]>>");
> > +MODULE_AUTHOR("Kevin Tang <[email protected]
> <mailto:[email protected]>>");
> > +MODULE_DESCRIPTION("Unisoc DRM KMS Master Driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/gpu/drm/sprd/sprd_drm.h
> b/drivers/gpu/drm/sprd/sprd_drm.h
> > new file mode 100644
> > index 0000000..e840e65
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 Unisoc Inc.
> > + */
> > +
> > +#ifndef _SPRD_DRM_H_
> > +#define _SPRD_DRM_H_
> > +
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_print.h>
> > +
> > +struct sprd_drm {
> > +     struct drm_device *drm;
> > +     struct drm_atomic_state *state;
> > +     struct device *dpu_dev;
> > +     struct device *gsp_dev;
> > +};
> > +
> > +#endif /* _SPRD_DRM_H_ */
> > diff --git a/drivers/gpu/drm/sprd/sprd_gem.c
> b/drivers/gpu/drm/sprd/sprd_gem.c
> > new file mode 100644
> > index 0000000..c617c8b
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_gem.c
> > @@ -0,0 +1,178 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2019 Unisoc Inc.
> > + */
> > +
> > +#include <linux/dma-buf.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <drm/drm_prime.h>
> > +
> > +#include "sprd_drm.h"
> > +#include "sprd_gem.h"
> > +
> > +static struct sprd_gem_obj *sprd_gem_obj_create(struct drm_device
> *drm,
> > +                                             unsigned long size)
> > +{
> > +     struct sprd_gem_obj *sprd_gem;
> > +     int ret;
> > +
> > +     sprd_gem = kzalloc(sizeof(*sprd_gem), GFP_KERNEL);
> > +     if (!sprd_gem)
> > +             return ERR_PTR(-ENOMEM);
> > +
> > +     ret = drm_gem_object_init(drm, &sprd_gem->base, size);
> > +     if (ret < 0) {
> > +             DRM_ERROR("failed to initialize gem object\n");
> > +             goto error;
> > +     }
> > +
> > +     ret = drm_gem_create_mmap_offset(&sprd_gem->base);
> > +     if (ret) {
> > +             drm_gem_object_release(&sprd_gem->base);
> > +             goto error;
> > +     }
> > +
> > +     return sprd_gem;
> > +
> > +error:
> > +     kfree(sprd_gem);
> > +     return ERR_PTR(ret);
> > +}
> > +
> > +void sprd_gem_free_object(struct drm_gem_object *obj)
> > +{
> > +     struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> > +
> > +     DRM_DEBUG("gem = %p\n", obj);
> > +
> > +     if (sprd_gem->vaddr)
> > +             dma_alloc_wc(obj->dev->dev, obj->size,
>
> dma_free_wc
>
> Thanks for reminding
>
>
> Best regards
> Thomas
>
> [1]
> https://cgit.freedesktop.org/drm/drm-tip/tree/drivers/gpu/drm/drm_gem_cma_helper.c
>
>
> > +                                   sprd_gem->vaddr,
> sprd_gem->dma_addr);
> > +     else if (sprd_gem->sgtb)
> > +             drm_prime_gem_destroy(obj, sprd_gem->sgtb);
> > +
> > +     drm_gem_object_release(obj);
> > +
> > +     kfree(sprd_gem);
> > +}
> > +
> > +int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct
> drm_device *drm,
> > +                         struct drm_mode_create_dumb *args)
> > +{
> > +     struct sprd_gem_obj *sprd_gem;
> > +     int ret;
> > +
> > +     args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> > +     args->size = round_up(args->pitch * args->height, PAGE_SIZE);
> > +
> > +     sprd_gem = sprd_gem_obj_create(drm, args->size);
> > +     if (IS_ERR(sprd_gem))
> > +             return PTR_ERR(sprd_gem);
> > +
> > +     sprd_gem->vaddr = dma_alloc_wc(drm->dev, args->size,
> > +                     &sprd_gem->dma_addr, GFP_KERNEL |
> __GFP_NOWARN | GFP_DMA);
> > +     if (!sprd_gem->vaddr) {
> > +             DRM_ERROR("failed to allocate buffer with size %llu\n",
> > +                       args->size);
> > +             ret = -ENOMEM;
> > +             goto error;
> > +     }
> > +
> > +     ret = drm_gem_handle_create(file_priv, &sprd_gem->base,
> &args->handle);
> > +     if (ret)
> > +             goto error;
> > +
> > +     drm_gem_object_put_unlocked(&sprd_gem->base);
> > +
> > +     return 0;
> > +
> > +error:
> > +     sprd_gem_free_object(&sprd_gem->base);
> > +     return ret;
> > +}
> > +
> > +static int sprd_gem_cma_object_mmap(struct drm_gem_object *obj,
> > +                                struct vm_area_struct *vma)
> > +
> > +{
> > +     int ret;
> > +     struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> > +
> > +     vma->vm_flags &= ~VM_PFNMAP;
> > +     vma->vm_pgoff = 0;
> > +
> > +     ret = dma_mmap_wc(obj->dev->dev, vma,
> > +                                 sprd_gem->vaddr, sprd_gem->dma_addr,
> > +                                 vma->vm_end - vma->vm_start);
> > +     if (ret)
> > +             drm_gem_vm_close(vma);
> > +
> > +     return ret;
> > +}
> > +
> > +int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma)
> > +{
> > +     struct drm_gem_object *obj;
> > +     int ret;
> > +
> > +     ret = drm_gem_mmap(filp, vma);
> > +     if (ret)
> > +             return ret;
> > +
> > +     obj = vma->vm_private_data;
> > +
> > +     return sprd_gem_cma_object_mmap(obj, vma);
> > +}
> > +
> > +int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
> > +                         struct vm_area_struct *vma)
> > +{
> > +     int ret;
> > +
> > +     ret = drm_gem_mmap_obj(obj, obj->size, vma);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return sprd_gem_cma_object_mmap(obj, vma);
> > +}
> > +
> > +struct sg_table *sprd_gem_cma_prime_get_sg_table(struct
> drm_gem_object *obj)
> > +{
> > +     struct sprd_gem_obj *sprd_gem = to_sprd_gem_obj(obj);
> > +     struct sg_table *sgtb;
> > +     int ret;
> > +
> > +     sgtb = kzalloc(sizeof(*sgtb), GFP_KERNEL);
> > +     if (!sgtb)
> > +             return ERR_PTR(-ENOMEM);
> > +
> > +     ret = dma_get_sgtable(obj->dev->dev, sgtb, sprd_gem->vaddr,
> > +                           sprd_gem->dma_addr, obj->size);
> > +     if (ret) {
> > +             DRM_ERROR("failed to allocate sg_table, %d\n", ret);
> > +             kfree(sgtb);
> > +             return ERR_PTR(ret);
> > +     }
> > +
> > +     return sgtb;
> > +}
> > +
> > +struct drm_gem_object *sprd_gem_prime_import_sg_table(struct
> drm_device *drm,
> > +             struct dma_buf_attachment *attach, struct sg_table
> *sgtb)
> > +{
> > +     struct sprd_gem_obj *sprd_gem;
> > +
> > +     sprd_gem = sprd_gem_obj_create(drm, attach->dmabuf->size);
> > +     if (IS_ERR(sprd_gem))
> > +             return ERR_CAST(sprd_gem);
> > +
> > +     DRM_DEBUG("gem = %p\n", &sprd_gem->base);
> > +
> > +     if (sgtb->nents == 1)
> > +             sprd_gem->dma_addr = sg_dma_address(sgtb->sgl);
> > +
> > +     sprd_gem->sgtb = sgtb;
> > +
> > +     return &sprd_gem->base;
> > +}
> > diff --git a/drivers/gpu/drm/sprd/sprd_gem.h
> b/drivers/gpu/drm/sprd/sprd_gem.h
> > new file mode 100644
> > index 0000000..4c10d8a
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_gem.h
> > @@ -0,0 +1,30 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 Unisoc Inc.
> > + */
> > +
> > +#ifndef _SPRD_GEM_H_
> > +#define _SPRD_GEM_H_
> > +
> > +#include <drm/drm_gem.h>
> > +
> > +struct sprd_gem_obj {
> > +     struct drm_gem_object   base;
> > +     dma_addr_t              dma_addr;
> > +     struct sg_table         *sgtb;
> > +     void                    *vaddr;
> > +};
> > +
> > +#define to_sprd_gem_obj(x)   container_of(x, struct sprd_gem_obj,
> base)
> > +
> > +void sprd_gem_free_object(struct drm_gem_object *gem);
> > +int sprd_gem_cma_dumb_create(struct drm_file *file_priv, struct
> drm_device *dev,
> > +                         struct drm_mode_create_dumb *args);
> > +int sprd_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma);
> > +int sprd_gem_cma_prime_mmap(struct drm_gem_object *obj,
> > +                      struct vm_area_struct *vma);
> > +struct sg_table *sprd_gem_cma_prime_get_sg_table(struct
> drm_gem_object *obj);
> > +struct drm_gem_object *sprd_gem_prime_import_sg_table(struct
> drm_device *dev,
> > +             struct dma_buf_attachment *attach, struct sg_table
> *sgtb);
> > +
> > +#endif
> >
>
> --
> Thomas Zimmermann
> Graphics Driver Developer
> SUSE Software Solutions Germany GmbH
> Maxfeldstr. 5, 90409 Nürnberg, Germany
> (HRB 36809, AG Nürnberg)
> Geschäftsführer: Felix Imendörffer
>

--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Maxfeldstr. 5, 90409 Nürnberg, Germany
(HRB 36809, AG Nürnberg)
Geschäftsführer: Felix Imendörffer


Attachments:
signature.asc (499.00 B)
OpenPGP digital signature

2019-12-10 16:10:15

by Emil Velikov

[permalink] [raw]
Subject: Re: [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master

Welcome to DRM Kevin,

On Tue, 10 Dec 2019 at 08:40, Kevin Tang <[email protected]> wrote:
>
> From: Kevin Tang <[email protected]>
>
> Adds drm support for the Unisoc's display subsystem.
>
> This is drm device and gem driver. This driver provides support for the
> Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
>
Did you use XFree86 or Xorg to test this? The XFree86 codebase have been
missing for years.

Out of curiosity - did you try any Wayland, or bare-metal compositor?

> source "drivers/gpu/drm/mcde/Kconfig"
>
> +source "drivers/gpu/drm/sprd/Kconfig"
> +
> # Keep legacy drivers last
>
> menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 9f1c7c4..85ca211 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -122,3 +122,4 @@ obj-$(CONFIG_DRM_LIMA) += lima/
> obj-$(CONFIG_DRM_PANFROST) += panfrost/
> obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
> obj-$(CONFIG_DRM_MCDE) += mcde/
> +obj-$(CONFIG_DRM_SPRD) += sprd/
> diff --git a/drivers/gpu/drm/sprd/Kconfig b/drivers/gpu/drm/sprd/Kconfig
> new file mode 100644
> index 0000000..79f286b
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/Kconfig
> @@ -0,0 +1,14 @@
> +config DRM_SPRD
> + tristate "DRM Support for Unisoc SoCs Platform"
> + depends on ARCH_SPRD
> + depends on DRM && OF
> + select DRM_KMS_HELPER
> + select DRM_GEM_CMA_HELPER
> + select DRM_KMS_CMA_HELPER
> + select DRM_MIPI_DSI
> + select DRM_PANEL
> + select VIDEOMODE_HELPERS
> + select BACKLIGHT_CLASS_DEVICE
> + help
> + Choose this option if you have a Unisoc chipsets.
s/chipsets/chipset/ ?


> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +ccflags-y += -Iinclude/drm
> +
> +subdir-ccflags-y += -I$(src)
> +
I think we can drop the includes here, unless there's a specific error
message that you're setting.


> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_drm.c

> +#define DRIVER_NAME "sprd"
> +#define DRIVER_DESC "Spreadtrum SoCs' DRM Driver"
> +#define DRIVER_DATE "20180501"
The date is mostly for cosmetic purposes. Yet we're nearly 2020 and
this reads 2018 - update?

<snip>

> +static struct drm_driver sprd_drm_drv = {
> + .driver_features = DRIVER_GEM | DRIVER_MODESET |
> + DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
There is no modeset exposed by the driver, let alone an atomic one.

Thus I would drop the following code from this patch and add it with a
patch that uses it.
- tokens - DRIVER_MODESET, DRIVER_ATOMIC)
- no-op modeset/atomic functions just above, and
- vblank/kms code (further down) in bind/unbind


<snip>

> +static int sprd_drm_probe(struct platform_device *pdev)
> +{
> + int ret;
> +
> + ret = dma_set_mask_and_coherent(&pdev->dev, ~0);
> + if (ret)
> + DRM_ERROR("dma_set_mask_and_coherent failed (%d)\n", ret);
> +
Is the hardware going to work correctly the dma call fails? Should we
use "return ret;" here?

> + return sprd_drm_component_probe(&pdev->dev, &sprd_drm_component_ops);
> +}
> +
> +static int sprd_drm_remove(struct platform_device *pdev)
> +{
> + component_master_del(&pdev->dev, &sprd_drm_component_ops);
> + return 0;
> +}
> +
> +static void sprd_drm_shutdown(struct platform_device *pdev)
> +{
> + struct drm_device *drm = platform_get_drvdata(pdev);
> +
> + if (!drm) {
Can this happen in reality?

<snip>

> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 Unisoc Inc.
> + */
> +
> +#ifndef _SPRD_DRM_H_
> +#define _SPRD_DRM_H_
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_print.h>
> +
> +struct sprd_drm {
> + struct drm_device *drm;

> + struct drm_atomic_state *state;
> + struct device *dpu_dev;
> + struct device *gsp_dev;
These three are unused. Please add alongside the code which is using them.


> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_gem.c

As Thomas pointed out, this is effectively a copy of drm_gem_cma_helper.c
Please drop this file and use the respective CMA functions, instead.


> diff --git a/drivers/gpu/drm/sprd/sprd_gem.h b/drivers/gpu/drm/sprd/sprd_gem.h
> new file mode 100644
> index 0000000..4c10d8a
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_gem.h
Just like the C file - this is effectively a copy of the existing CMA codebase.

HTH
Emil

2019-12-10 17:15:48

by Emil Velikov

[permalink] [raw]
Subject: Re: [PATCH RFC 4/8] drm/sprd: add Unisoc's drm display controller driver

Hi Kevin,

On Tue, 10 Dec 2019 at 08:41, Kevin Tang <[email protected]> wrote:
>
> From: Kevin Tang <[email protected]>
>
> Adds DPU(Display Processor Unit) support for the Unisoc's display subsystem.
> It's support multi planes, scaler, rotation, PQ(Picture Quality) and more.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> drivers/gpu/drm/sprd/Makefile | 6 +-
> drivers/gpu/drm/sprd/disp_lib.c | 290 +++++++
> drivers/gpu/drm/sprd/disp_lib.h | 40 +
> drivers/gpu/drm/sprd/dpu/Makefile | 8 +
> drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 1464 +++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_dpu.c | 1152 +++++++++++++++++++++++++++
> drivers/gpu/drm/sprd/sprd_dpu.h | 217 ++++++
> 7 files changed, 3176 insertions(+), 1 deletion(-)

As we can see from the diff stat this patch is huge. So it would be fairly hard
to provide meaningful review as-is.

One can combine my earlier suggestion (to keep modeset/atomic out of 2/8), with
the following split:
- 4/8 add basic atomic modeset support - one format, one rotation 0, no extra
attributes
- 5/8 add extra formats
- 6/8 add extra rotation support
- ... add custom attributes

<snip>

> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/disp_lib.c

> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/disp_lib.h

Let's keep this code out, for now. If we really need it we could revive/add it
at a later stage.

<snip>

> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> @@ -0,0 +1,1464 @@


> +struct gamma_entry {
> + u16 r;
> + u16 g;
> + u16 b;
> +};
> +
Seem to be unused. Please drop this and double-check for other unused structs


> +static struct scale_cfg scale_copy;
> +static struct cm_cfg cm_copy;
> +static struct slp_cfg slp_copy;
> +static struct gamma_lut gamma_copy;
> +static struct hsv_lut hsv_copy;
> +static struct epf_cfg epf_copy;
> +static u32 enhance_en;
> +
> +static DECLARE_WAIT_QUEUE_HEAD(wait_queue);
> +static bool panel_ready = true;
> +static bool need_scale;
> +static bool mode_changed;
> +static bool evt_update;
> +static bool evt_stop;
> +static u32 prev_y2r_coef;
> +
We should really avoid static variables. Some of the above look like enhancer
state. One could create a struct keeping it alongside the rest of the display
pipeline, right?


<snip>

> +static void dpu_enhance_backup(u32 id, void *param)
> +{
As the enhance code is fairly large, lets keep the portions to a separate patch.


<snip>

> +static struct dpu_core_ops dpu_r2p0_ops = {
Nit: as a general rule ops should be const.


> +static int __init dpu_core_register(void)
> +{
> + return dpu_core_ops_register(&entry);
> +}
> +
> +subsys_initcall(dpu_core_register);
I think that subsys_initcall, __init and MODULE area applicable only
when we have multiple
modules. Not 100% sure though ;-)


> diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c b/drivers/gpu/drm/sprd/sprd_dpu.c
> new file mode 100644
> index 0000000..43142b3
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_dpu.c

> +struct sprd_plane {
> + struct drm_plane plane;
> + struct drm_property *alpha_property;
> + struct drm_property *blend_mode_property;
Core DRM already has alpha and blend_mode properties. Please reuse the code


> + struct drm_property *fbc_hsize_r_property;
> + struct drm_property *fbc_hsize_y_property;
> + struct drm_property *fbc_hsize_uv_property;
> + struct drm_property *y2r_coef_property;
> + struct drm_property *pallete_en_property;
> + struct drm_property *pallete_color_property;
Let's have these properties introduced with separate follow-up patches.
Please mention, in the commit message, why they are specific to the driver.


<snip>

> +static int sprd_dpu_init(struct sprd_dpu *dpu)
> +{
> + struct dpu_context *ctx = &dpu->ctx;
> +
> + down(&ctx->refresh_lock);
> +
> + if (dpu->ctx.is_inited) {
> + up(&ctx->refresh_lock);
> + return 0;
> + }
> +
> + if (dpu->glb && dpu->glb->power)
> + dpu->glb->power(ctx, true);
> + if (dpu->glb && dpu->glb->enable)
> + dpu->glb->enable(ctx);
> +
> + if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
> + dpu->glb->reset(ctx);
> +
> + if (dpu->clk && dpu->clk->init)
> + dpu->clk->init(ctx);
> + if (dpu->clk && dpu->clk->enable)
> + dpu->clk->enable(ctx);
> +
> + if (dpu->core && dpu->core->init)
> + dpu->core->init(ctx);
> + if (dpu->core && dpu->core->ifconfig)
> + dpu->core->ifconfig(ctx);
> +
Hmm I can see the core, clk and glb (ops) being added to the respective lists.
Yet the code which adds those to the dpu isn't so obvious. Where is it?


> + ctx->is_inited = true;
> +
Nit: is_inited -> initialized



<snip>

> +struct dpu_core_ops {
> + int (*parse_dt)(struct dpu_context *ctx,
> + struct device_node *np);
> + u32 (*version)(struct dpu_context *ctx);
> + int (*init)(struct dpu_context *ctx);
> + void (*uninit)(struct dpu_context *ctx);
> + void (*run)(struct dpu_context *ctx);
> + void (*stop)(struct dpu_context *ctx);
> + void (*disable_vsync)(struct dpu_context *ctx);
> + void (*enable_vsync)(struct dpu_context *ctx);
> + u32 (*isr)(struct dpu_context *ctx);
> + void (*ifconfig)(struct dpu_context *ctx);
> + void (*write_back)(struct dpu_context *ctx, u8 count, bool debug);
> + void (*flip)(struct dpu_context *ctx,
> + struct sprd_dpu_layer layers[], u8 count);
> + int (*capability)(struct dpu_context *ctx,
> + struct dpu_capability *cap);
> + void (*bg_color)(struct dpu_context *ctx, u32 color);
> + void (*enhance_set)(struct dpu_context *ctx, u32 id, void *param);
> + void (*enhance_get)(struct dpu_context *ctx, u32 id, void *param);
> + int (*modeset)(struct dpu_context *ctx,
> + struct drm_mode_modeinfo *mode);
> + bool (*check_raw_int)(struct dpu_context *ctx, u32 mask);
> +};
> +
> +struct dpu_clk_ops {
> + int (*parse_dt)(struct dpu_context *ctx,
> + struct device_node *np);
> + int (*init)(struct dpu_context *ctx);
> + int (*uinit)(struct dpu_context *ctx);
> + int (*enable)(struct dpu_context *ctx);
> + int (*disable)(struct dpu_context *ctx);
> + int (*update)(struct dpu_context *ctx, int clk_id, int val);
> +};
> +
> +struct dpu_glb_ops {
> + int (*parse_dt)(struct dpu_context *ctx,
> + struct device_node *np);
> + void (*enable)(struct dpu_context *ctx);
> + void (*disable)(struct dpu_context *ctx);
> + void (*reset)(struct dpu_context *ctx);
> + void (*power)(struct dpu_context *ctx, int enable);
> +};
> +
Some of the above seem unused - parse_dt for example. Please drop the dead code.


HTH
Emil

2019-12-10 18:55:48

by Sam Ravnborg

[permalink] [raw]
Subject: Re: [PATCH RFC 0/8] Add Unisoc's drm kms module

Hi Kevin.

Thanks for this new driver.
A few somments triggered alone from the diffstat.
> Kevin Tang (8):
> dt-bindings: display: add Unisoc's drm master bindings
> drm/sprd: add Unisoc's drm kms master
> dt-bindings: display: add Unisoc's dpu bindings
> drm/sprd: add Unisoc's drm display controller driver
> dt-bindings: display: add Unisoc's mipi dsi&dphy bindings
> drm/sprd: add Unisoc's drm mipi dsi&dphy driver
> dt-bindings: display: add Unisoc's generic mipi panel bindings
> drm/sprd: add Unisoc's drm generic mipi panel driver
>
> .../devicetree/bindings/display/sprd/dphy.txt | 49 +
> .../devicetree/bindings/display/sprd/dpu.txt | 55 +
> .../devicetree/bindings/display/sprd/drm.txt | 18 +
> .../devicetree/bindings/display/sprd/dsi.txt | 68 +
> .../devicetree/bindings/display/sprd/panel.txt | 110 ++

New bindings in metaschema format (.yaml).
And panel bindings in display/panel/

> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/sprd/Kconfig | 14 +
> drivers/gpu/drm/sprd/Makefile | 17 +
> drivers/gpu/drm/sprd/disp_lib.c | 290 ++++
> drivers/gpu/drm/sprd/disp_lib.h | 40 +
> drivers/gpu/drm/sprd/dphy/Makefile | 7 +
> drivers/gpu/drm/sprd/dphy/pll/Makefile | 3 +
> drivers/gpu/drm/sprd/dphy/pll/megacores_sharkle.c | 640 +++++++++
> drivers/gpu/drm/sprd/dphy/sprd_dphy_api.c | 254 ++++
> drivers/gpu/drm/sprd/dphy/sprd_dphy_hal.h | 329 +++++
> drivers/gpu/drm/sprd/dpu/Makefile | 8 +
> drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 1464 ++++++++++++++++++++
> drivers/gpu/drm/sprd/dsi/Makefile | 7 +
> drivers/gpu/drm/sprd/dsi/core/Makefile | 3 +
> drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.c | 1186 ++++++++++++++++
> drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0.h | 1417 +++++++++++++++++++
> drivers/gpu/drm/sprd/dsi/core/dsi_ctrl_r1p0_ppi.c | 392 ++++++
> drivers/gpu/drm/sprd/dsi/sprd_dsi_api.c | 544 ++++++++
> drivers/gpu/drm/sprd/dsi/sprd_dsi_api.h | 28 +
> drivers/gpu/drm/sprd/dsi/sprd_dsi_hal.h | 1102 +++++++++++++++
> drivers/gpu/drm/sprd/sprd_dphy.c | 235 ++++
> drivers/gpu/drm/sprd/sprd_dphy.h | 121 ++
> drivers/gpu/drm/sprd/sprd_dpu.c | 1152 +++++++++++++++
> drivers/gpu/drm/sprd/sprd_dpu.h | 217 +++
> drivers/gpu/drm/sprd/sprd_drm.c | 287 ++++
> drivers/gpu/drm/sprd/sprd_drm.h | 19 +
> drivers/gpu/drm/sprd/sprd_dsi.c | 722 ++++++++++
> drivers/gpu/drm/sprd/sprd_dsi.h | 210 +++
> drivers/gpu/drm/sprd/sprd_gem.c | 178 +++
> drivers/gpu/drm/sprd/sprd_gem.h | 30 +

> drivers/gpu/drm/sprd/sprd_panel.c | 778 +++++++++++
> drivers/gpu/drm/sprd/sprd_panel.h | 114 ++
Please move panels to drivers/gpu/drm/panel/ and make sure to utilize
the drm_panel infrastructure.

Sam

2019-12-10 22:04:51

by Daniel Vetter

[permalink] [raw]
Subject: Re: [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master

On Tue, Dec 10, 2019 at 04:06:53PM +0000, Emil Velikov wrote:
> Welcome to DRM Kevin,
>
> On Tue, 10 Dec 2019 at 08:40, Kevin Tang <[email protected]> wrote:
> >
> > From: Kevin Tang <[email protected]>
> >
> > Adds drm support for the Unisoc's display subsystem.
> >
> > This is drm device and gem driver. This driver provides support for the
> > Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
> >
> Did you use XFree86 or Xorg to test this? The XFree86 codebase have been
> missing for years.
>
> Out of curiosity - did you try any Wayland, or bare-metal compositor?

Just noticed that the driver also seems to be missing fbdev emulation.
Given that that is a one-line (for a proper driver using all the right
helpers) it should be added. See drm_fbdev_generic_setup().
-Daniel

>
> > source "drivers/gpu/drm/mcde/Kconfig"
> >
> > +source "drivers/gpu/drm/sprd/Kconfig"
> > +
> > # Keep legacy drivers last
> >
> > menuconfig DRM_LEGACY
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 9f1c7c4..85ca211 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -122,3 +122,4 @@ obj-$(CONFIG_DRM_LIMA) += lima/
> > obj-$(CONFIG_DRM_PANFROST) += panfrost/
> > obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
> > obj-$(CONFIG_DRM_MCDE) += mcde/
> > +obj-$(CONFIG_DRM_SPRD) += sprd/
> > diff --git a/drivers/gpu/drm/sprd/Kconfig b/drivers/gpu/drm/sprd/Kconfig
> > new file mode 100644
> > index 0000000..79f286b
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/Kconfig
> > @@ -0,0 +1,14 @@
> > +config DRM_SPRD
> > + tristate "DRM Support for Unisoc SoCs Platform"
> > + depends on ARCH_SPRD
> > + depends on DRM && OF
> > + select DRM_KMS_HELPER
> > + select DRM_GEM_CMA_HELPER
> > + select DRM_KMS_CMA_HELPER
> > + select DRM_MIPI_DSI
> > + select DRM_PANEL
> > + select VIDEOMODE_HELPERS
> > + select BACKLIGHT_CLASS_DEVICE
> > + help
> > + Choose this option if you have a Unisoc chipsets.
> s/chipsets/chipset/ ?
>
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/Makefile
> > @@ -0,0 +1,8 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +ccflags-y += -Iinclude/drm
> > +
> > +subdir-ccflags-y += -I$(src)
> > +
> I think we can drop the includes here, unless there's a specific error
> message that you're setting.
>
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_drm.c
>
> > +#define DRIVER_NAME "sprd"
> > +#define DRIVER_DESC "Spreadtrum SoCs' DRM Driver"
> > +#define DRIVER_DATE "20180501"
> The date is mostly for cosmetic purposes. Yet we're nearly 2020 and
> this reads 2018 - update?
>
> <snip>
>
> > +static struct drm_driver sprd_drm_drv = {
> > + .driver_features = DRIVER_GEM | DRIVER_MODESET |
> > + DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
> There is no modeset exposed by the driver, let alone an atomic one.
>
> Thus I would drop the following code from this patch and add it with a
> patch that uses it.
> - tokens - DRIVER_MODESET, DRIVER_ATOMIC)
> - no-op modeset/atomic functions just above, and
> - vblank/kms code (further down) in bind/unbind
>
>
> <snip>
>
> > +static int sprd_drm_probe(struct platform_device *pdev)
> > +{
> > + int ret;
> > +
> > + ret = dma_set_mask_and_coherent(&pdev->dev, ~0);
> > + if (ret)
> > + DRM_ERROR("dma_set_mask_and_coherent failed (%d)\n", ret);
> > +
> Is the hardware going to work correctly the dma call fails? Should we
> use "return ret;" here?
>
> > + return sprd_drm_component_probe(&pdev->dev, &sprd_drm_component_ops);
> > +}
> > +
> > +static int sprd_drm_remove(struct platform_device *pdev)
> > +{
> > + component_master_del(&pdev->dev, &sprd_drm_component_ops);
> > + return 0;
> > +}
> > +
> > +static void sprd_drm_shutdown(struct platform_device *pdev)
> > +{
> > + struct drm_device *drm = platform_get_drvdata(pdev);
> > +
> > + if (!drm) {
> Can this happen in reality?
>
> <snip>
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 Unisoc Inc.
> > + */
> > +
> > +#ifndef _SPRD_DRM_H_
> > +#define _SPRD_DRM_H_
> > +
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_print.h>
> > +
> > +struct sprd_drm {
> > + struct drm_device *drm;
>
> > + struct drm_atomic_state *state;
> > + struct device *dpu_dev;
> > + struct device *gsp_dev;
> These three are unused. Please add alongside the code which is using them.
>
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_gem.c
>
> As Thomas pointed out, this is effectively a copy of drm_gem_cma_helper.c
> Please drop this file and use the respective CMA functions, instead.
>
>
> > diff --git a/drivers/gpu/drm/sprd/sprd_gem.h b/drivers/gpu/drm/sprd/sprd_gem.h
> > new file mode 100644
> > index 0000000..4c10d8a
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_gem.h
> Just like the C file - this is effectively a copy of the existing CMA codebase.
>
> HTH
> Emil

--
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

2019-12-11 11:48:34

by Emil Velikov

[permalink] [raw]
Subject: Re: [PATCH RFC 4/8] drm/sprd: add Unisoc's drm display controller driver

On Wed, 11 Dec 2019 at 09:18, tang pengchuan <[email protected]> wrote:
>
> Hi
>
> Emil Velikov <[email protected]> 于2019年12月11日周三 上午1:14写道:
>>
>> Hi Kevin,
>>
>> On Tue, 10 Dec 2019 at 08:41, Kevin Tang <[email protected]> wrote:
>> >
>> > From: Kevin Tang <[email protected]>
>> >
>> > Adds DPU(Display Processor Unit) support for the Unisoc's display subsystem.
>> > It's support multi planes, scaler, rotation, PQ(Picture Quality) and more.
>> >
>> > Cc: Orson Zhai <[email protected]>
>> > Cc: Baolin Wang <[email protected]>
>> > Cc: Chunyan Zhang <[email protected]>
>> > Signed-off-by: Kevin Tang <[email protected]>
>> > ---
>> > drivers/gpu/drm/sprd/Makefile | 6 +-
>> > drivers/gpu/drm/sprd/disp_lib.c | 290 +++++++
>> > drivers/gpu/drm/sprd/disp_lib.h | 40 +
>> > drivers/gpu/drm/sprd/dpu/Makefile | 8 +
>> > drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 1464 +++++++++++++++++++++++++++++++++++
>> > drivers/gpu/drm/sprd/sprd_dpu.c | 1152 +++++++++++++++++++++++++++
>> > drivers/gpu/drm/sprd/sprd_dpu.h | 217 ++++++
>> > 7 files changed, 3176 insertions(+), 1 deletion(-)
>>
>> As we can see from the diff stat this patch is huge. So it would be fairly hard
>> to provide meaningful review as-is.
>>
>> One can combine my earlier suggestion (to keep modeset/atomic out of 2/8), with
>> the following split:
>> - 4/8 add basic atomic modeset support - one format, one rotation 0, no extra
>> attributes
>> - 5/8 add extra formats
>> - 6/8 add extra rotation support
>> - ... add custom attributes
>
> Ok, i will split this patch, upstream modeset and atomic at first. clock, gloabl, enhance, extra
> attributes and so on will be upload later.

Amazing thank you. Please apply the similar logic and split patch 6/8
- that patch is twice larger than this one.

Some small general requests - please use plain text emails (see [1])
and trim unrelated fragments when replying.
Otherwise it is very easy to miss the comment that you and others have made.

HTH
Emil

[1] https://www.lifewire.com/how-to-send-a-message-in-plain-text-from-gmail-1171963

2019-12-13 09:44:10

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH RFC 1/8] dt-bindings: display: add Unisoc's drm master bindings

Hi,

On Tue, Dec 10, 2019 at 04:36:28PM +0800, Kevin Tang wrote:
> From: Kevin Tang <[email protected]>
>
> The Unisoc DRM master device is a virtual device needed to list all
> DPU devices or other display interface nodes that comprise the
> graphics subsystem
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> Documentation/devicetree/bindings/display/sprd/drm.txt | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/sprd/drm.txt
>
> diff --git a/Documentation/devicetree/bindings/display/sprd/drm.txt b/Documentation/devicetree/bindings/display/sprd/drm.txt
> new file mode 100644
> index 0000000..7327b9e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/sprd/drm.txt
> @@ -0,0 +1,18 @@
> +Unisoc DRM master device
> +================================
> +
> +The Unisoc DRM master device is a virtual device needed to list all
> +DPU devices or other display interface nodes that comprise the
> +graphics subsystem.
> +
> +Required properties:
> +- compatible: Should be "sprd,display-subsystem"
> +- ports: Should contain a list of phandles pointing to display interface port
> + of DPU devices.
> +
> +example:
> +
> +display-subsystem {
> + compatible = "sprd,display-subsystem";
> + ports = <&dpu_out>;
> +};

You should be using YAML schemas to describe your bindings nowadays.

Also, I'm not sure about how the display engine (and its component) is
going to be integrated into the SoCs, but usually have the name of the
first SoC to use it is a good practice in the case where there's a
second generation of display engine coming up in the future.

Maxime


Attachments:
(No filename) (1.80 kB)
signature.asc (235.00 B)
Download all attachments

2019-12-13 09:45:10

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH RFC 3/8] dt-bindings: display: add Unisoc's dpu bindings

Hi,

On Tue, Dec 10, 2019 at 04:36:30PM +0800, Kevin Tang wrote:
> From: Kevin Tang <[email protected]>
>
> DPU (Display Processor Unit) is the Display Controller for the Unisoc SoCs
> which transfers the image data from a video memory buffer to an internal
> LCD interface.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> .../devicetree/bindings/display/sprd/dpu.txt | 55 ++++++++++++++++++++++
> 1 file changed, 55 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/sprd/dpu.txt
>
> diff --git a/Documentation/devicetree/bindings/display/sprd/dpu.txt b/Documentation/devicetree/bindings/display/sprd/dpu.txt
> new file mode 100644
> index 0000000..25cbf8e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/sprd/dpu.txt
> @@ -0,0 +1,55 @@
> +Unisoc SoC Display Processor Unit (DPU)
> +============================================================================
> +
> +DPU (Display Processor Unit) is the Display Controller for the Unisoc SoCs
> +which transfers the image data from a video memory buffer to an internal
> +LCD interface.
> +
> +Required properties:
> + - compatible: value should be "sprd,display-processor";
> + - reg: physical base address and length of the DPU registers set.
> + - interrupts: the interrupt signal from DPU.
> + - clocks: must include clock specifiers corresponding to entries in the
> + clock-names property.
> + - clock-names: list of clock names sorted in the same order as the clocks
> + property.

Same story, this should be a YAML schemas, but here you should
describe what the expected clock-names are, and what clock they
represent.

> + - dma-coherent: with this property, the dpu driver can allocate large and
> + continuous memorys.
> + - port: a port node with endpoint definitions as defined in document [1].

So only one? Connected to what?

Maxime


Attachments:
(No filename) (2.03 kB)
signature.asc (235.00 B)
Download all attachments

2019-12-13 09:47:13

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH RFC 5/8] dt-bindings: display: add Unisoc's mipi dsi&dphy bindings

On Tue, Dec 10, 2019 at 04:36:32PM +0800, Kevin Tang wrote:
> From: Kevin Tang <[email protected]>
>
> Adds MIPI DSI Master and MIPI DSI-PHY (D-PHY)
> support for Unisoc's display subsystem.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> .../devicetree/bindings/display/sprd/dphy.txt | 49 ++++++++++++++++
> .../devicetree/bindings/display/sprd/dsi.txt | 68 ++++++++++++++++++++++
> 2 files changed, 117 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/sprd/dphy.txt
> create mode 100644 Documentation/devicetree/bindings/display/sprd/dsi.txt
>
> diff --git a/Documentation/devicetree/bindings/display/sprd/dphy.txt b/Documentation/devicetree/bindings/display/sprd/dphy.txt
> new file mode 100644
> index 0000000..474c2b2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/sprd/dphy.txt
> @@ -0,0 +1,49 @@
> +Unisoc MIPI DSI-PHY (D-PHY)
> +============================================================================
> +
> +Required properties:
> + - compatible: value should be "sprd,dsi-phy".
> + - reg: must be the dsi controller base address.
> + - #address-cells, #size-cells: should be set respectively to <1> and <0>
> +
> +Video interfaces:
> + Device node can contain video interface port nodes according to [1].
> + The following are properties specific to those nodes:
> +
> + port node inbound:
> + - reg: (required) must be 0.
> + port node outbound:
> + - reg: (required) must be 1.
> +
> + endpoint node connected from DSI controller node (reg = 0):
> + - remote-endpoint: specifies the endpoint in DSI node.
> + endpoint node connected to panel node (reg = 1):
> + - remote-endpoint: specifies the endpoint in panel node.
> +
> +[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
> +
> +
> +Example:
> +
> + dphy: dphy {
> + compatible = "sprd,dsi-phy";
> + reg = <0x0 0x63100000 0x0 0x1000>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + /* input port*/
> + port@1 {
> + reg = <1>;
> + dphy_in: endpoint {
> + remote-endpoint = <&dsi_out>;
> + };
> + };
> +
> + /* output port */
> + port@0 {
> + reg = <0>;
> + dphy_out: endpoint {
> + remote-endpoint = <&panel_in>;
> + };
> + };
> + };

There's support for the MIPI D-PHY in the Linux PHY framework now, so
it would be a good fit for this (and that would mean using the generic
PHY bindings too.

> diff --git a/Documentation/devicetree/bindings/display/sprd/dsi.txt b/Documentation/devicetree/bindings/display/sprd/dsi.txt
> new file mode 100644
> index 0000000..1719ff5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/sprd/dsi.txt
> @@ -0,0 +1,68 @@
> +Unisoc MIPI DSI Master
> +=============================================================================
> +
> +Required properties:
> + - compatible: value should be "sprd,dsi-host";
> + - reg: physical base address and length of the registers set for the device
> + - interrupts: should contain DSI interrupt
> + - clocks: list of clock specifiers, must contain an entry for each required
> + entry in clock-names
> + - clock-names: list of clock names sorted in the same order as the clocks
> + property.

Again, a list of the expected clock names and what they represent
would be great here.

Thanks!
Maxime


Attachments:
(No filename) (3.47 kB)
signature.asc (235.00 B)
Download all attachments

2019-12-13 09:50:09

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH RFC 7/8] dt-bindings: display: add Unisoc's generic mipi panel bindings

On Tue, Dec 10, 2019 at 04:36:34PM +0800, Kevin Tang wrote:
> From: Kevin Tang <[email protected]>
>
> Adds generic MIPI panel support for Unisoc's display subsystem.
>
> Cc: Orson Zhai <[email protected]>
> Cc: Baolin Wang <[email protected]>
> Cc: Chunyan Zhang <[email protected]>
> Signed-off-by: Kevin Tang <[email protected]>
> ---
> .../devicetree/bindings/display/sprd/panel.txt | 110 +++++++++++++++++++++
> 1 file changed, 110 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/sprd/panel.txt
>
> diff --git a/Documentation/devicetree/bindings/display/sprd/panel.txt b/Documentation/devicetree/bindings/display/sprd/panel.txt
> new file mode 100644
> index 0000000..a4017af
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/sprd/panel.txt
> @@ -0,0 +1,110 @@
> +Unisoc Generic MIPI Panel
> +================================================================
> +
> +Required properties:
> + - compatible: must be "sprd,generic-mipi-panel"
> + - reg: panel ID
> + - #address-cells, #size-cells: should be set respectively to <1> and <0>
> + - port: video port for DPI input
> + - sprd,dsi-work-mode: the following dsi mode can be select:
> + 0: cmd mode,
> + 1: video burst mode,
> + 2: video non-burst mode with sync pulse,
> + 3: video non-burst mode with sync event
> + - sprd,dsi-lane-number: number of dsi lanes to use, default is 4
> + - sprd,dsi-color-format: data format for video stream transmission, currently
> + supports "rgb888", "rgb666", "rgb666_packed", "rgb565" and "dsc", defaults rgb888
> + - sprd,phy-bit-clock: the transmission rate of the clock lane for High-Speed,
> + the unit is Kbps, and the default value is 500Mbps
> + - sprd,phy-escape-clock: the transmission rate of the clock lane for Low-Power,
> + the unit is Kbps, and the default value is 20Mbps
> +
> +
> +Optional properties:
> + - width-mm: see [2] for details
> + - height-mm: see [2] for details
> + - sprd,esd-check-enable: esd check function enable switch
> + - sprd,esd-check-mode: esd detection method, default is register
> + 0: register,
> + 1: TE signal
> + - sprd,esd-check-period: esd detection cycle, unit ms, default 1000ms
> + - sprd,esd-check-register: if register detection is used, this attribute must be configured
> + - sprd,esd-check-value: if register detection is used, this attribute must be configured
> + - sprd,reset-on-sequence: timing of the reset pin when the lcd power on
> + <1 5>, <0 5> means first keep high for 5ms, then keep low for 5ms
> + - sprd,reset-on-sequence: timing of the reset pin when the lcd power off
> + - sprd,use-dcs-write: bool attribute, indicating whether to use the dcs to send inital & sleep cmds,
> + default use generic
> + - sprd,initial-command: lcd initialization command set
> + - sprd,sleep-in-command: lcd suspend command set
> + - sprd,sleep-out-command: lcd resume command set
> + - display-timings: see [1] for details

I can't say for sure since I'm not the panel maintainer, but I'm not
sure it's something that we want.

Panels are much more complicated than that, and DT is usually to store
data, not code (unlike the initial-command property you have).

The best way to support this would be to use the panel infrastructure.

> +
> + [1] Documentation/devicetree/bindings/display/panel/display-timing.txt
> + [2] Documentation/devicetree/bindings/display/panel/panel-common.yaml
> +
> +Example
> +-------
> +
> +Panel specific DT entry:
> +
> + &dsi {
> + panel {
> + compatible = "sprd,generic-mipi-panel";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = <0>;
> +
> + port {
> + reg = <1>;
> + panel_in: endpoint {
> + remote-endpoint = <&dphy_out>;
> + };
> + };
> + };
> + };
> +
> + / { lcds {
> + lcd_mipi_hd: lcd_mipi_hd {
> + sprd,dsi-work-mode = <1>;
> + sprd,dsi-lane-number = <4>;
> + sprd,dsi-color-format = "rgb888";
> + sprd,phy-bit-clock = <1100000>;
> + sprd,phy-escape-clock = <20000>;
> + width-mm = <68>;
> + height-mm = <121>;
> + sprd,esd-check-enable = <0>;
> + sprd,esd-check-mode = <0>;
> + sprd,esd-check-period = <1000>;
> + sprd,esd-check-register = <0x0A>;
> + sprd,esd-check-value = <0x9C>;
> + sprd,reset-on-sequence = <1 5>, <0 5>, <1 20>;
> + sprd,reset-off-sequence = <0 5>;
> + sprd,use-dcs-write;
> + sprd,initial-command = [
> + 39 00 00 02 b0 00
> + 39 00 00 04 B3 31 00 06
> + ];
> + sprd,sleep-in-command = [
> + 13 0A 00 01 28
> + 13 78 00 01 10
> + ];
> + sprd,sleep-out-command = [
> + 13 78 00 01 11
> + 13 32 00 01 29
> + ];
> + display-timings {
> + timing0 {
> + clock-frequency = <64000000>;
> + hactive = <720>;
> + vactive = <1280>;
> + hback-porch = <31>;
> + hfront-porch = <31>;
> + vback-porch = <32>;
> + vfront-porch = <16>;
> + hsync-len = <20>;
> + vsync-len = <2>;
> + };
> + };
> + };

This example doesn't match the binding described above?

Thanks!
Maxime


Attachments:
(No filename) (5.76 kB)
signature.asc (235.00 B)
Download all attachments