Allwinner's recent SoCs, as A64, A83T and H3, contain a new display
engine, DE2.
This patch adds a DRM video driver for this device.

Signed-off-by: Jean-Francois Moine <moin...@free.fr>
---
 drivers/gpu/drm/Kconfig           |   2 +
 drivers/gpu/drm/Makefile          |   1 +
 drivers/gpu/drm/sun8i/Kconfig     |  19 +
 drivers/gpu/drm/sun8i/Makefile    |   7 +
 drivers/gpu/drm/sun8i/de2_crtc.c  | 449 +++++++++++++++++++++++
 drivers/gpu/drm/sun8i/de2_crtc.h  |  50 +++
 drivers/gpu/drm/sun8i/de2_drv.c   | 317 ++++++++++++++++
 drivers/gpu/drm/sun8i/de2_drv.h   |  48 +++
 drivers/gpu/drm/sun8i/de2_plane.c | 734 ++++++++++++++++++++++++++++++++++++++
 9 files changed, 1627 insertions(+)
 create mode 100644 drivers/gpu/drm/sun8i/Kconfig
 create mode 100644 drivers/gpu/drm/sun8i/Makefile
 create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.c
 create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.h
 create mode 100644 drivers/gpu/drm/sun8i/de2_drv.c
 create mode 100644 drivers/gpu/drm/sun8i/de2_drv.h
 create mode 100644 drivers/gpu/drm/sun8i/de2_plane.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 95fc041..bb1bfbc 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -202,6 +202,8 @@ source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/sun4i/Kconfig"
 
+source "drivers/gpu/drm/sun8i/Kconfig"
+
 source "drivers/gpu/drm/omapdrm/Kconfig"
 
 source "drivers/gpu/drm/tilcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 883f3e7..3e1eaa0 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-y                  += omapdrm/
 obj-$(CONFIG_DRM_SUN4I) += sun4i/
+obj-$(CONFIG_DRM_SUN8I) += sun8i/
 obj-y                  += tilcdc/
 obj-$(CONFIG_DRM_QXL) += qxl/
 obj-$(CONFIG_DRM_BOCHS) += bochs/
diff --git a/drivers/gpu/drm/sun8i/Kconfig b/drivers/gpu/drm/sun8i/Kconfig
new file mode 100644
index 0000000..6940895
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/Kconfig
@@ -0,0 +1,19 @@
+#
+# Allwinner DE2 Video configuration
+#
+
+config DRM_SUN8I
+       bool
+
+config DRM_SUN8I_DE2
+       tristate "Support for Allwinner Video with DE2 interface"
+       depends on DRM && OF
+       depends on ARCH_SUNXI || COMPILE_TEST
+       select DRM_GEM_CMA_HELPER
+       select DRM_KMS_CMA_HELPER
+       select DRM_KMS_HELPER
+       select DRM_SUN8I
+       help
+         Choose this option if your Allwinner chipset has the DE2 interface
+         as the A64, A83T and H3. If M is selected the module will be called
+         sun8i-de2-drm.
diff --git a/drivers/gpu/drm/sun8i/Makefile b/drivers/gpu/drm/sun8i/Makefile
new file mode 100644
index 0000000..f107919
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for Allwinner's sun8i DRM device driver
+#
+
+sun8i-de2-drm-objs := de2_drv.o de2_crtc.o de2_plane.o
+
+obj-$(CONFIG_DRM_SUN8I_DE2) += sun8i-de2-drm.o
diff --git a/drivers/gpu/drm/sun8i/de2_crtc.c b/drivers/gpu/drm/sun8i/de2_crtc.c
new file mode 100644
index 0000000..4e94ccc
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_crtc.c
@@ -0,0 +1,449 @@
+/*
+ * Allwinner DRM driver - DE2 CRTC
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moin...@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/component.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <linux/io.h>
+#include <linux/of_irq.h>
+#include <linux/of_graph.h>
+
+#include "de2_drv.h"
+#include "de2_crtc.h"
+
+/* I/O map */
+
+#define TCON_GCTL_REG          0x00
+#define                TCON_GCTL_TCON_ENABLE BIT(31)
+#define TCON_GINT0_REG         0x04
+#define                TCON_GINT0_TCON1_Vb_Int_En BIT(30)
+#define                TCON_GINT0_TCON1_Vb_Int_Flag BIT(14)
+#define                TCON_GINT0_TCON1_Vb_Line_Int_Flag BIT(12)
+#define TCON0_CTL_REG          0x40
+#define                TCON0_CTL_TCON_ENABLE BIT(31)
+#define TCON1_CTL_REG          0x90
+#define                TCON1_CTL_TCON_ENABLE BIT(31)
+#define                TCON1_CTL_INTERLACE_ENABLE BIT(20)
+#define                TCON1_CTL_Start_Delay_SHIFT 4
+#define                TCON1_CTL_Start_Delay_MASK GENMASK(8, 4)
+#define TCON1_BASIC0_REG       0x94    /* XI/YI */
+#define TCON1_BASIC1_REG       0x98    /* LS_XO/LS_YO */
+#define TCON1_BASIC2_REG       0x9c    /* XO/YO */
+#define TCON1_BASIC3_REG       0xa0    /* HT/HBP */
+#define TCON1_BASIC4_REG       0xa4    /* VT/VBP */
+#define TCON1_BASIC5_REG       0xa8    /* HSPW/VSPW */
+#define TCON1_PS_SYNC_REG      0xb0
+#define TCON1_IO_POL_REG       0xf0
+#define                TCON1_IO_POL_IO0_inv BIT(24)
+#define                TCON1_IO_POL_IO1_inv BIT(25)
+#define                TCON1_IO_POL_IO2_inv BIT(26)
+#define TCON1_IO_TRI_REG       0xf4
+#define TCON_CEU_CTL_REG       0x100
+#define                TCON_CEU_CTL_ceu_en BIT(31)
+#define        TCON1_FILL_CTL_REG      0x300
+#define TCON1_FILL_START0_REG  0x304
+#define TCON1_FILL_END0_REG    0x308
+#define TCON1_FILL_DATA0_REG   0x30c
+
+#define XY(x, y) (((x) << 16) | (y))
+
+#define andl_relaxed(addr, val) \
+       writel_relaxed(readl_relaxed(addr) & val, addr)
+#define orl_relaxed(addr, val) \
+       writel_relaxed(readl_relaxed(addr) | val, addr)
+
+/* vertical blank functions */
+
+static void de2_atomic_flush(struct drm_crtc *crtc,
+                       struct drm_crtc_state *old_state)
+{
+       struct drm_pending_vblank_event *event = crtc->state->event;
+
+       if (event) {
+               crtc->state->event = NULL;
+               spin_lock_irq(&crtc->dev->event_lock);
+               if (drm_crtc_vblank_get(crtc) == 0)
+                       drm_crtc_arm_vblank_event(crtc, event);
+               else
+                       drm_crtc_send_vblank_event(crtc, event);
+               spin_unlock_irq(&crtc->dev->event_lock);
+       }
+}
+
+static irqreturn_t de2_lcd_irq(int irq, void *dev_id)
+{
+       struct lcd *lcd = (struct lcd *) dev_id;
+       u32 isr;
+
+       isr = readl_relaxed(lcd->mmio + TCON_GINT0_REG);
+
+       drm_crtc_handle_vblank(&lcd->crtc);
+
+       writel_relaxed(isr &
+                       ~(TCON_GINT0_TCON1_Vb_Int_Flag |
+                         TCON_GINT0_TCON1_Vb_Line_Int_Flag),
+                       lcd->mmio + TCON_GINT0_REG);
+
+       return IRQ_HANDLED;
+}
+
+int de2_enable_vblank(struct drm_device *drm, unsigned int crtc_ix)
+{
+       struct priv *priv = drm_to_priv(drm);
+       struct lcd *lcd = priv->lcds[crtc_ix];
+
+       orl_relaxed(lcd->mmio + TCON_GINT0_REG, TCON_GINT0_TCON1_Vb_Int_En);
+
+       return 0;
+}
+
+void de2_disable_vblank(struct drm_device *drm, unsigned int crtc_ix)
+{
+       struct priv *priv = drm_to_priv(drm);
+       struct lcd *lcd = priv->lcds[crtc_ix];
+
+       andl_relaxed(lcd->mmio + TCON_GINT0_REG, ~TCON_GINT0_TCON1_Vb_Int_En);
+}
+
+void de2_vblank_reset(struct lcd *lcd)
+{
+       drm_crtc_vblank_reset(&lcd->crtc);
+}
+
+/* frame functions */
+static int de2_crtc_set_clock(struct lcd *lcd, int rate)
+{
+       struct clk *parent_clk;
+       u32 parent_rate;
+       int ret;
+
+       /* determine and set the best rate for the parent clock (pll-video) */
+       if ((270000 * 2) % rate == 0)
+               parent_rate = 270000000;
+       else if (297000 % rate == 0)
+               parent_rate = 297000000;
+       else
+               return -EINVAL;                 /* unsupported clock */
+
+       parent_clk = clk_get_parent(lcd->clk);
+
+       ret = clk_set_rate(parent_clk, parent_rate);
+       if (ret) {
+               dev_err(lcd->dev, "set parent rate failed %d\n", ret);
+               return ret;
+       }
+       ret = clk_set_rate(lcd->clk, rate * 1000);
+       if (ret) {
+               dev_err(lcd->dev, "set rate failed %d\n", ret);
+               return ret;
+       }
+
+       /* enable the clock */
+       reset_control_deassert(lcd->reset);
+       clk_prepare_enable(lcd->bus);
+       clk_prepare_enable(lcd->clk);
+
+       return ret;
+}
+
+static void de2_tcon_init(struct lcd *lcd)
+{
+       andl_relaxed(lcd->mmio + TCON0_CTL_REG, ~TCON0_CTL_TCON_ENABLE);
+       andl_relaxed(lcd->mmio + TCON1_CTL_REG, ~TCON1_CTL_TCON_ENABLE);
+       andl_relaxed(lcd->mmio + TCON_GCTL_REG, ~TCON_GCTL_TCON_ENABLE);
+
+       /* disable/ack interrupts */
+       writel_relaxed(0, lcd->mmio + TCON_GINT0_REG);
+}
+
+static void de2_tcon_enable(struct lcd *lcd)
+{
+       struct drm_crtc *crtc = &lcd->crtc;
+       const struct drm_display_mode *mode = &crtc->mode;
+       int interlace = mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 1;
+       int start_delay;
+       u32 data;
+
+       orl_relaxed(lcd->mmio + TCON_GCTL_REG, TCON_GCTL_TCON_ENABLE);
+
+       data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1);
+       writel_relaxed(data, lcd->mmio + TCON1_BASIC0_REG);
+       writel_relaxed(data, lcd->mmio + TCON1_BASIC1_REG);
+       writel_relaxed(data, lcd->mmio + TCON1_BASIC2_REG);
+       writel_relaxed(XY(mode->htotal - 1,
+                        mode->htotal - mode->hsync_start - 1),
+                     lcd->mmio + TCON1_BASIC3_REG);
+       writel_relaxed(XY(mode->vtotal * (3 - interlace),
+                        mode->vtotal - mode->vsync_start - 1),
+                     lcd->mmio + TCON1_BASIC4_REG);
+       writel_relaxed(XY(mode->hsync_end - mode->hsync_start - 1,
+                        mode->vsync_end - mode->vsync_start - 1),
+                     lcd->mmio + TCON1_BASIC5_REG);
+
+       data = TCON1_IO_POL_IO2_inv;
+       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+               data |= TCON1_IO_POL_IO0_inv;
+       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+               data |= TCON1_IO_POL_IO1_inv;
+       writel_relaxed(data, lcd->mmio + TCON1_IO_POL_REG);
+
+       andl_relaxed(lcd->mmio + TCON_CEU_CTL_REG, ~TCON_CEU_CTL_ceu_en);
+
+       if (interlace == 2)
+               orl_relaxed(lcd->mmio + TCON1_CTL_REG,
+                           TCON1_CTL_INTERLACE_ENABLE);
+       else
+               andl_relaxed(lcd->mmio + TCON1_CTL_REG,
+                            ~TCON1_CTL_INTERLACE_ENABLE);
+
+       writel_relaxed(0, lcd->mmio + TCON1_FILL_CTL_REG);
+       writel_relaxed(mode->vtotal + 1, lcd->mmio + TCON1_FILL_START0_REG);
+       writel_relaxed(mode->vtotal, lcd->mmio + TCON1_FILL_END0_REG);
+       writel_relaxed(0, lcd->mmio + TCON1_FILL_DATA0_REG);
+
+       start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5;
+       if (start_delay > 31)
+               start_delay = 31;
+       data = readl_relaxed(lcd->mmio + TCON1_CTL_REG);
+       data &= ~TCON1_CTL_Start_Delay_MASK;
+       data |= start_delay << TCON1_CTL_Start_Delay_SHIFT;
+       writel_relaxed(data, lcd->mmio + TCON1_CTL_REG);
+
+       orl_relaxed(lcd->mmio + TCON1_CTL_REG, TCON1_CTL_TCON_ENABLE);
+}
+
+static void de2_tcon_disable(struct lcd *lcd)
+{
+       andl_relaxed(lcd->mmio + TCON1_CTL_REG, ~TCON1_CTL_TCON_ENABLE);
+       andl_relaxed(lcd->mmio + TCON_GCTL_REG, ~TCON_GCTL_TCON_ENABLE);
+}
+
+static void de2_crtc_enable(struct drm_crtc *crtc)
+{
+       struct lcd *lcd = crtc_to_lcd(crtc);
+       struct drm_display_mode *mode = &crtc->mode;
+
+       if (de2_crtc_set_clock(lcd, mode->clock) < 0)
+               return;
+       lcd->clk_enabled = true;
+
+       /* start the TCON and the DE */
+       de2_tcon_enable(lcd);
+       de2_de_enable(lcd);
+
+       /* turn on blanking interrupt */
+       drm_crtc_vblank_on(crtc);
+}
+
+static void de2_crtc_disable(struct drm_crtc *crtc,
+                               struct drm_crtc_state *old_crtc_state)
+{
+       struct lcd *lcd = crtc_to_lcd(crtc);
+
+       if (!lcd->clk_enabled)
+               return;                 /* already disabled */
+       lcd->clk_enabled = false;
+
+       de2_de_disable(lcd);
+
+       drm_crtc_vblank_off(crtc);
+
+       de2_tcon_disable(lcd);
+
+       clk_disable_unprepare(lcd->clk);
+       clk_disable_unprepare(lcd->bus);
+       reset_control_assert(lcd->reset);
+}
+
+static const struct drm_crtc_funcs de2_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,
+};
+
+static const struct drm_crtc_helper_funcs de2_crtc_helper_funcs = {
+       .atomic_flush   = de2_atomic_flush,
+       .enable         = de2_crtc_enable,
+       .atomic_disable = de2_crtc_disable,
+};
+
+/* device init */
+static int de2_lcd_bind(struct device *dev, struct device *master,
+                       void *data)
+{
+       struct drm_device *drm = data;
+       struct priv *priv = drm_to_priv(drm);
+       struct lcd *lcd = dev_get_drvdata(dev);
+       struct drm_crtc *crtc = &lcd->crtc;
+       int ret, i, crtc_ix;
+
+       lcd->priv = priv;
+
+       /* set the CRTC reference */
+       crtc_ix = drm_crtc_index(crtc);
+       if (crtc_ix >= ARRAY_SIZE(priv->lcds)) {
+               dev_err(drm->dev, "Bad crtc index");
+               return -ENOENT;
+       }
+       priv->lcds[crtc_ix] = lcd;
+
+       /* and the mixer index (DT port index in the DE) */
+       for (i = 0; ; i++) {
+               struct device_node *port;
+
+               port = of_parse_phandle(drm->dev->of_node, "ports", i);
+               if (!port)
+                       break;
+               if (port == lcd->crtc.port) {
+                       lcd->mixer = i;
+                       break;
+               }
+       }
+
+       ret = de2_plane_init(drm, lcd);
+       if (ret < 0)
+               return ret;
+
+       drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs);
+
+       return drm_crtc_init_with_planes(drm, crtc,
+                                        &lcd->planes[DE2_PRIMARY_PLANE],
+                                        &lcd->planes[DE2_CURSOR_PLANE],
+                                        &de2_crtc_funcs, NULL);
+}
+
+static void de2_lcd_unbind(struct device *dev, struct device *master,
+                          void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct lcd *lcd = platform_get_drvdata(pdev);
+
+       if (lcd->priv)
+               lcd->priv->lcds[drm_crtc_index(&lcd->crtc)] = NULL;
+}
+
+static const struct component_ops de2_lcd_ops = {
+       .bind = de2_lcd_bind,
+       .unbind = de2_lcd_unbind,
+};
+
+static int de2_lcd_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node, *tmp, *parent, *port;
+       struct lcd *lcd;
+       struct resource *res;
+       int id, irq, ret;
+
+       lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL);
+       if (!lcd)
+               return -ENOMEM;
+
+       dev_set_drvdata(dev, lcd);
+       lcd->dev = dev;
+       lcd->mixer = id;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(dev, "failed to get memory resource\n");
+               return -EINVAL;
+       }
+
+       lcd->mmio = devm_ioremap_resource(dev, res);
+       if (IS_ERR(lcd->mmio)) {
+               dev_err(dev, "failed to map registers\n");
+               return PTR_ERR(lcd->mmio);
+       }
+
+       /* possible CRTC */
+       parent = np;
+       tmp = of_get_child_by_name(np, "ports");
+       if (tmp)
+               parent = tmp;
+       port = of_get_child_by_name(parent, "port");
+       of_node_put(tmp);
+       if (!port) {
+               dev_err(dev, "no port node\n");
+               return -ENXIO;
+       }
+       lcd->crtc.port = port;
+
+       lcd->bus = devm_clk_get(dev, "bus");
+       if (IS_ERR(lcd->bus)) {
+               dev_err(dev, "get bus clock err %d\n", (int) PTR_ERR(lcd->bus));
+               ret = PTR_ERR(lcd->bus);
+               goto err;
+       }
+
+       lcd->clk = devm_clk_get(dev, "clock");
+       if (IS_ERR(lcd->clk)) {
+               ret = PTR_ERR(lcd->clk);
+               dev_err(dev, "get video clock err %d\n", ret);
+               goto err;
+       }
+
+       lcd->reset = devm_reset_control_get(dev, NULL);
+       if (IS_ERR(lcd->reset)) {
+               ret = PTR_ERR(lcd->reset);
+               dev_err(dev, "get reset err %d\n", ret);
+               goto err;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq <= 0) {
+               dev_err(dev, "unable to get irq\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       de2_tcon_init(lcd);             /* stop TCON and avoid interrupts */
+
+       ret = devm_request_irq(dev, irq, de2_lcd_irq, 0,
+                               dev_name(dev), lcd);
+       if (ret < 0) {
+               dev_err(dev, "unable to request irq %d\n", irq);
+               goto err;
+       }
+
+       return component_add(dev, &de2_lcd_ops);
+
+err:
+       of_node_put(lcd->crtc.port);
+       return ret;
+}
+
+static int de2_lcd_remove(struct platform_device *pdev)
+{
+       struct lcd *lcd = platform_get_drvdata(pdev);
+
+       component_del(&pdev->dev, &de2_lcd_ops);
+
+       of_node_put(lcd->crtc.port);
+
+       return 0;
+}
+
+static const struct of_device_id de2_lcd_ids[] = {
+       { .compatible = "allwinner,sun8i-a83t-tcon", },
+       { }
+};
+
+struct platform_driver de2_lcd_platform_driver = {
+       .probe = de2_lcd_probe,
+       .remove = de2_lcd_remove,
+       .driver = {
+               .name = "sun8i-de2-tcon",
+               .of_match_table = of_match_ptr(de2_lcd_ids),
+       },
+};
diff --git a/drivers/gpu/drm/sun8i/de2_crtc.h b/drivers/gpu/drm/sun8i/de2_crtc.h
new file mode 100644
index 0000000..c0d34a7
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_crtc.h
@@ -0,0 +1,50 @@
+#ifndef __DE2_CRTC_H__
+#define __DE2_CRTC_H__
+/*
+ * Copyright (C) 2016 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drm_plane_helper.h>
+
+struct clk;
+struct reset_control;
+struct priv;
+
+/* planes */
+#define DE2_PRIMARY_PLANE 0
+#define DE2_CURSOR_PLANE 1
+#define DE2_N_PLANES 5 /* number of planes - see plane_tb[] in de2_plane.c */
+
+struct lcd {
+       void __iomem *mmio;
+
+       struct device *dev;
+       struct drm_crtc crtc;
+
+       struct priv *priv;      /* DRM/DE private data */
+
+       u8 mixer;               /* LCD (mixer) number */
+       u8 delayed;             /* bitmap of planes with delayed update */
+
+       u8 clk_enabled;         /* used for error in crtc_enable */
+
+       struct clk *clk;
+       struct clk *bus;
+       struct reset_control *reset;
+
+       struct drm_plane planes[DE2_N_PLANES];
+};
+
+#define crtc_to_lcd(x) container_of(x, struct lcd, crtc)
+
+/* in de2_plane.c */
+void de2_de_enable(struct lcd *lcd);
+void de2_de_disable(struct lcd *lcd);
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd);
+
+#endif /* __DE2_CRTC_H__ */
diff --git a/drivers/gpu/drm/sun8i/de2_drv.c b/drivers/gpu/drm/sun8i/de2_drv.c
new file mode 100644
index 0000000..f96babe
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_drv.c
@@ -0,0 +1,317 @@
+/*
+ * Allwinner DRM driver - DE2 DRM driver
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moin...@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <drm/drm_of.h>
+#include <linux/component.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "de2_drv.h"
+
+#define DRIVER_NAME    "sun8i-de2"
+#define DRIVER_DESC    "Allwinner DRM DE2"
+#define DRIVER_DATE    "20161101"
+#define DRIVER_MAJOR   1
+#define DRIVER_MINOR   0
+
+static const struct of_device_id de2_drm_of_match[] = {
+       { .compatible = "allwinner,sun8i-a83t-display-engine",
+                               .data = (void *) SOC_A83T },
+       { .compatible = "allwinner,sun8i-h3-display-engine",
+                               .data = (void *) SOC_H3 },
+       { },
+};
+MODULE_DEVICE_TABLE(of, de2_drm_of_match);
+
+static void de2_fb_output_poll_changed(struct drm_device *drm)
+{
+       struct priv *priv = drm_to_priv(drm);
+
+       if (priv->fbdev)
+               drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs de2_mode_config_funcs = {
+       .fb_create = drm_fb_cma_create,
+       .output_poll_changed = de2_fb_output_poll_changed,
+       .atomic_check = drm_atomic_helper_check,
+       .atomic_commit = drm_atomic_helper_commit,
+};
+
+/* -- DRM operations -- */
+
+static void de2_lastclose(struct drm_device *drm)
+{
+       struct priv *priv = drm_to_priv(drm);
+
+       if (priv->fbdev)
+               drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static const struct file_operations de2_fops = {
+       .owner          = THIS_MODULE,
+       .open           = drm_open,
+       .release        = drm_release,
+       .unlocked_ioctl = drm_ioctl,
+       .poll           = drm_poll,
+       .read           = drm_read,
+       .llseek         = no_llseek,
+       .mmap           = drm_gem_cma_mmap,
+};
+
+static struct drm_driver de2_drm_driver = {
+       .driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+                                       DRIVER_ATOMIC,
+       .lastclose              = de2_lastclose,
+       .get_vblank_counter     = drm_vblank_no_hw_counter,
+       .enable_vblank          = de2_enable_vblank,
+       .disable_vblank         = de2_disable_vblank,
+       .gem_free_object        = drm_gem_cma_free_object,
+       .gem_vm_ops             = &drm_gem_cma_vm_ops,
+       .prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
+       .gem_prime_import       = drm_gem_prime_import,
+       .gem_prime_export       = drm_gem_prime_export,
+       .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+       .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+       .gem_prime_vmap         = drm_gem_cma_prime_vmap,
+       .gem_prime_vunmap       = drm_gem_cma_prime_vunmap,
+       .gem_prime_mmap         = drm_gem_cma_prime_mmap,
+       .dumb_create            = drm_gem_cma_dumb_create,
+       .dumb_map_offset        = drm_gem_cma_dumb_map_offset,
+       .dumb_destroy           = drm_gem_dumb_destroy,
+       .fops                   = &de2_fops,
+       .name                   = DRIVER_NAME,
+       .desc                   = DRIVER_DESC,
+       .date                   = DRIVER_DATE,
+       .major                  = DRIVER_MAJOR,
+       .minor                  = DRIVER_MINOR,
+};
+
+/*
+ * Platform driver
+ */
+
+static int de2_drm_bind(struct device *dev)
+{
+       struct drm_device *drm;
+       struct priv *priv;
+       struct resource *res;
+       struct lcd *lcd;
+       int i, ret;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       drm = &priv->drm;
+       dev_set_drvdata(dev, drm);
+
+       /* get the resources */
+       priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data;
+
+       res = platform_get_resource(to_platform_device(dev),
+                               IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(dev, "failed to get memory resource\n");
+               ret = -EINVAL;
+               goto out1;
+       }
+
+       priv->mmio = devm_ioremap_resource(dev, res);
+       if (IS_ERR(priv->mmio)) {
+               ret = PTR_ERR(priv->mmio);
+               dev_err(dev, "failed to map registers %d\n", ret);
+               goto out1;
+       }
+
+       priv->gate = devm_clk_get(dev, "bus");
+       if (IS_ERR(priv->gate)) {
+               ret = PTR_ERR(priv->gate);
+               dev_err(dev, "bus gate err %d\n", ret);
+               goto out1;
+       }
+
+       priv->clk = devm_clk_get(dev, "clock");
+       if (IS_ERR(priv->clk)) {
+               ret = PTR_ERR(priv->clk);
+               dev_err(dev, "clock err %d\n", ret);
+               goto out1;
+       }
+
+       priv->reset = devm_reset_control_get(dev, NULL);
+       if (IS_ERR(priv->reset)) {
+               ret = PTR_ERR(priv->reset);
+               dev_err(dev, "reset err %d\n", ret);
+               goto out1;
+       }
+
+       mutex_init(&priv->mutex);       /* protect DE I/O accesses */
+
+       ret = drm_dev_init(drm, &de2_drm_driver, dev);
+       if (ret != 0) {
+               dev_err(dev, "dev_init failed %d\n", ret);
+               goto out1;
+       }
+
+       drm_mode_config_init(drm);
+       drm->mode_config.min_width = 32;        /* needed for cursor */
+       drm->mode_config.min_height = 32;
+       drm->mode_config.max_width = 1920;
+       drm->mode_config.max_height = 1080;
+       drm->mode_config.funcs = &de2_mode_config_funcs;
+
+       drm->irq_enabled = true;
+
+       /* start the subdevices */
+       ret = component_bind_all(dev, drm);
+       if (ret < 0)
+               goto out2;
+
+       /* initialize and disable vertical blanking on all CRTCs */
+       ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+       if (ret < 0)
+               dev_warn(dev, "vblank_init failed %d\n", ret);
+
+       for (i = 0; i < ARRAY_SIZE(priv->lcds); i++) {
+               lcd = priv->lcds[i];
+               if (lcd)
+                       de2_vblank_reset(lcd);
+       }
+
+       drm_mode_config_reset(drm);
+
+       priv->fbdev = drm_fbdev_cma_init(drm,
+                                        32,    /* bpp */
+                                        drm->mode_config.num_crtc,
+                                        drm->mode_config.num_connector);
+       if (IS_ERR(priv->fbdev)) {
+               ret = PTR_ERR(priv->fbdev);
+               priv->fbdev = NULL;
+               goto out3;
+       }
+
+       drm_kms_helper_poll_init(drm);
+
+       ret = drm_dev_register(drm, 0);
+       if (ret < 0)
+               goto out4;
+
+       return 0;
+
+out4:
+       drm_fbdev_cma_fini(priv->fbdev);
+out3:
+       component_unbind_all(dev, drm);
+out2:
+       drm_dev_unref(drm);
+out1:
+       kfree(priv);
+       return ret;
+}
+
+static void de2_drm_unbind(struct device *dev)
+{
+       struct drm_device *drm = dev_get_drvdata(dev);
+       struct priv *priv = drm_to_priv(drm);
+
+       drm_dev_unregister(drm);
+
+       drm_fbdev_cma_fini(priv->fbdev);
+       drm_kms_helper_poll_fini(drm);
+       drm_vblank_cleanup(drm);
+       drm_mode_config_cleanup(drm);
+
+       component_unbind_all(dev, drm);
+
+       kfree(priv);
+}
+
+static const struct component_master_ops de2_drm_comp_ops = {
+       .bind = de2_drm_bind,
+       .unbind = de2_drm_unbind,
+};
+
+/*
+ * drm_of_component_probe() does:
+ * - bind of the ports (lcd-controller.port)
+ * - bind of the remote nodes (hdmi, tve..)
+ */
+static int compare_of(struct device *dev, void *data)
+{
+       struct device_node *np = data;
+
+       if (of_node_cmp(np->name, "port") == 0) {
+               np = of_get_parent(np);
+               of_node_put(np);
+       }
+       return dev->of_node == np;
+}
+
+static int de2_drm_probe(struct platform_device *pdev)
+{
+       int ret;
+
+       ret = drm_of_component_probe(&pdev->dev,
+                                    compare_of,
+                                    &de2_drm_comp_ops);
+       if (ret == -EINVAL)
+               ret = -ENXIO;
+       return ret;
+}
+
+static int de2_drm_remove(struct platform_device *pdev)
+{
+       component_master_del(&pdev->dev, &de2_drm_comp_ops);
+
+       return 0;
+}
+
+static struct platform_driver de2_drm_platform_driver = {
+       .probe      = de2_drm_probe,
+       .remove     = de2_drm_remove,
+       .driver     = {
+               .name = DRIVER_NAME,
+               .of_match_table = de2_drm_of_match,
+       },
+};
+
+static int __init de2_drm_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&de2_lcd_platform_driver);
+       if (ret < 0)
+               return ret;
+
+       ret = platform_driver_register(&de2_drm_platform_driver);
+       if (ret < 0)
+               platform_driver_unregister(&de2_lcd_platform_driver);
+
+       return ret;
+}
+
+static void __exit de2_drm_fini(void)
+{
+       platform_driver_unregister(&de2_lcd_platform_driver);
+       platform_driver_unregister(&de2_drm_platform_driver);
+}
+
+module_init(de2_drm_init);
+module_exit(de2_drm_fini);
+
+MODULE_AUTHOR("Jean-Francois Moine <moin...@free.fr>");
+MODULE_DESCRIPTION("Allwinner DE2 DRM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sun8i/de2_drv.h b/drivers/gpu/drm/sun8i/de2_drv.h
new file mode 100644
index 0000000..c42c30a
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_drv.h
@@ -0,0 +1,48 @@
+#ifndef __DE2_DRM_H__
+#define __DE2_DRM_H__
+/*
+ * Copyright (C) 2016 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+
+struct drm_fbdev_cma;
+struct lcd;
+
+#define N_LCDS 2
+
+struct priv {
+       struct drm_device drm;
+       void __iomem *mmio;
+       struct clk *clk;
+       struct clk *gate;
+       struct reset_control *reset;
+
+       struct mutex mutex;     /* protect DE I/O access */
+       u8 soc_type;
+#define SOC_A83T 0
+#define SOC_H3 1
+       u8 started;             /* bitmap of started mixers */
+       u8 clean;               /* bitmap of clean mixers */
+
+       struct drm_fbdev_cma *fbdev;
+
+       struct lcd *lcds[N_LCDS]; /* CRTCs */
+};
+
+#define drm_to_priv(x) container_of(x, struct priv, drm)
+
+/* in de2_crtc.c */
+int de2_enable_vblank(struct drm_device *drm, unsigned int crtc);
+void de2_disable_vblank(struct drm_device *drm, unsigned int crtc);
+void de2_vblank_reset(struct lcd *lcd);
+extern struct platform_driver de2_lcd_platform_driver;
+
+#endif /* __DE2_DRM_H__ */
diff --git a/drivers/gpu/drm/sun8i/de2_plane.c 
b/drivers/gpu/drm/sun8i/de2_plane.c
new file mode 100644
index 0000000..2fd72dc
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_plane.c
@@ -0,0 +1,734 @@
+/*
+ * Allwinner DRM driver - Display Engine 2
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moin...@free.fr>
+ * Adapted from the sun8iw6 and sun8iw7 disp2 drivers
+ *     Copyright (c) 2016 Allwinnertech Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/io.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "de2_drv.h"
+#include "de2_crtc.h"
+
+/* DE2 I/O map */
+
+#define DE2_MOD_REG 0x0000             /* 1 bit per LCD */
+#define DE2_GATE_REG 0x0004
+#define DE2_RESET_REG 0x0008
+#define DE2_DIV_REG 0x000c             /* 4 bits per LCD */
+#define DE2_SEL_REG 0x0010
+
+#define DE2_MIXER0_BASE 0x00100000     /* LCD 0 */
+#define DE2_MIXER1_BASE 0x00200000     /* LCD 1 */
+
+/* mixer registers (addr / mixer base) */
+#define MIXER_GLB_REGS 0x00000         /* global control */
+#define MIXER_BLD_REGS 0x01000         /* alpha blending */
+#define MIXER_CHAN_REGS 0x02000                /* VI/UI overlay channels */
+#define                MIXER_CHAN_SZ 0x1000    /* size of a channel */
+#define MIXER_VSU_REGS 0x20000         /* VSU */
+#define MIXER_GSU1_REGS 0x30000                /* GSUs */
+#define MIXER_GSU2_REGS 0x40000
+#define MIXER_GSU3_REGS 0x50000
+#define MIXER_FCE_REGS 0xa0000         /* FCE */
+#define MIXER_BWS_REGS 0xa2000         /* BWS */
+#define MIXER_LTI_REGS 0xa4000         /* LTI */
+#define MIXER_PEAK_REGS 0xa6000                /* PEAK */
+#define MIXER_ASE_REGS 0xa8000         /* ASE */
+#define MIXER_FCC_REGS 0xaa000         /* FCC */
+#define MIXER_DCSC_REGS 0xb0000                /* DCSC/SMBL */
+
+/* global control */
+#define MIXER_GLB_CTL_REG      0x00
+#define                MIXER_GLB_CTL_rt_en BIT(0)
+#define                MIXER_GLB_CTL_finish_irq_en BIT(4)
+#define                MIXER_GLB_CTL_rtwb_port BIT(12)
+#define MIXER_GLB_STATUS_REG   0x04
+#define MIXER_GLB_DBUFF_REG    0x08
+#define MIXER_GLB_SIZE_REG     0x0c
+
+/* alpha blending */
+#define MIXER_BLD_FCOLOR_CTL_REG       0x00
+#define                MIXER_BLD_FCOLOR_CTL_PEN(pipe)  (0x0100 << (pipe))
+#define        MIXER_BLD_ATTR_N 4              /* number of attribute blocks */
+#define        MIXER_BLD_ATTR_SIZE (4 * 4)     /* size of an attribute block */
+#define MIXER_BLD_ATTRx_FCOLOR(x)      (0x04 + MIXER_BLD_ATTR_SIZE * (x))
+#define MIXER_BLD_ATTRx_INSIZE(x)      (0x08 + MIXER_BLD_ATTR_SIZE * (x))
+#define MIXER_BLD_ATTRx_OFFSET(x)      (0x0c + MIXER_BLD_ATTR_SIZE * (x))
+#define MIXER_BLD_ROUTE_REG    0x80
+#define                MIXER_BLD_ROUTE(chan, pipe) ((chan) << ((pipe) * 4))
+#define MIXER_BLD_PREMULTIPLY_REG      0x84
+#define MIXER_BLD_BKCOLOR_REG  0x88
+#define MIXER_BLD_OUTPUT_SIZE_REG      0x8c
+#define MIXER_BLD_MODEx_REG(x) (0x90 + 4 * (x))        /* x = 0..3 */
+#define                MIXER_BLD_MODE_SRCOVER  0x03010301
+#define MIXER_BLD_OUT_CTL_REG  0xfc
+
+/* VI channel (channel 0) */
+#define VI_CFG_N               4               /* number of layers */
+#define VI_CFG_SIZE            0x30            /* size of a layer */
+#define VI_CFGx_ATTR(l)                (0x00 + VI_CFG_SIZE * (l))
+#define                VI_CFG_ATTR_en BIT(0)
+#define                VI_CFG_ATTR_fcolor_en BIT(4)
+#define                VI_CFG_ATTR_fmt_SHIFT 8
+#define                VI_CFG_ATTR_fmt_MASK GENMASK(12, 8)
+#define                VI_CFG_ATTR_ui_sel BIT(15)
+#define                VI_CFG_ATTR_top_down BIT(23)
+#define VI_CFGx_SIZE(l)                (0x04 + VI_CFG_SIZE * (l))
+#define VI_CFGx_COORD(l)       (0x08 + VI_CFG_SIZE * (l))
+#define VI_N_PLANES 3
+#define VI_CFGx_PITCHy(l, p)   (0x0c + VI_CFG_SIZE * (l) + 4 * (p))
+#define VI_CFGx_TOP_LADDRy(l, p) (0x18 + VI_CFG_SIZE * (l) + 4 * (p))
+#define VI_CFGx_BOT_LADDRy(l, p) (0x24 + VI_CFG_SIZE * (l) + 4 * (p))
+#define VI_FCOLORx(l)          (0xc0 + 4 * (l))
+#define VI_TOP_HADDRx(p)       (0xd0 + 4 * (p))
+#define VI_BOT_HADDRx(p)       (0xdc + 4 * (p))
+#define VI_OVL_SIZEx(n)                (0xe8 + 4 * (n))
+#define VI_HORI_DSx(n)         (0xf0 + 4 * (n))
+#define VI_VERT_DSx(n)         (0xf8 + 4 * (n))
+#define VI_SIZE                        0x100
+
+/* UI channel (channels 1..3) */
+#define UI_CFG_N               4               /* number of layers */
+#define UI_CFG_SIZE            (8 * 4)         /* size of a layer */
+#define UI_CFGx_ATTR(l)                (0x00 + UI_CFG_SIZE * (l))
+#define                UI_CFG_ATTR_en BIT(0)
+#define                UI_CFG_ATTR_alpmod_SHIFT 1
+#define                UI_CFG_ATTR_alpmod_MASK GENMASK(2, 1)
+#define                UI_CFG_ATTR_fcolor_en BIT(4)
+#define                UI_CFG_ATTR_fmt_SHIFT 8
+#define                UI_CFG_ATTR_fmt_MASK GENMASK(12, 8)
+#define                UI_CFG_ATTR_top_down BIT(23)
+#define                UI_CFG_ATTR_alpha_SHIFT 24
+#define                UI_CFG_ATTR_alpha_MASK GENMASK(31, 24)
+#define UI_CFGx_SIZE(l)                (0x04 + UI_CFG_SIZE * (l))
+#define UI_CFGx_COORD(l)       (0x08 + UI_CFG_SIZE * (l))
+#define UI_CFGx_PITCH(l)       (0x0c + UI_CFG_SIZE * (l))
+#define UI_CFGx_TOP_LADDR(l)   (0x10 + UI_CFG_SIZE * (l))
+#define UI_CFGx_BOT_LADDR(l)   (0x14 + UI_CFG_SIZE * (l))
+#define UI_CFGx_FCOLOR(l)      (0x18 + UI_CFG_SIZE * (l))
+#define UI_TOP_HADDR           0x80
+#define UI_BOT_HADDR           0x84
+#define UI_OVL_SIZE            0x88
+#define UI_SIZE                        0x8c
+
+/* coordinates and sizes */
+#define XY(x, y) (((y) << 16) | (x))
+#define WH(w, h) ((((h) - 1) << 16) | ((w) - 1))
+
+/* UI video formats */
+#define DE2_FORMAT_ARGB_8888 0
+#define DE2_FORMAT_BGRA_8888 3
+#define DE2_FORMAT_XRGB_8888 4
+#define DE2_FORMAT_RGB_888 8
+#define DE2_FORMAT_BGR_888 9
+
+/* VI video formats */
+#define DE2_FORMAT_YUV422_I_YVYU 1     /* YVYU */
+#define DE2_FORMAT_YUV422_I_UYVY 2     /* UYVY */
+#define DE2_FORMAT_YUV422_I_YUYV 3     /* YUYV */
+#define DE2_FORMAT_YUV422_P 6          /* YYYY UU VV planar */
+#define DE2_FORMAT_YUV420_P 10         /* YYYY U V planar */
+
+/* plane formats */
+static const uint32_t ui_formats[] = {
+       DRM_FORMAT_ARGB8888,
+       DRM_FORMAT_BGRA8888,
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_RGB888,
+       DRM_FORMAT_BGR888,
+};
+
+static const uint32_t vi_formats[] = {
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_YUYV,
+       DRM_FORMAT_YVYU,
+       DRM_FORMAT_YUV422,
+       DRM_FORMAT_YUV420,
+       DRM_FORMAT_UYVY,
+       DRM_FORMAT_BGRA8888,
+       DRM_FORMAT_RGB888,
+       DRM_FORMAT_BGR888,
+};
+
+/*
+ * plane table
+ *
+ * The chosen channel/layer assignment of the planes respects
+ * the following constraints:
+ * - the cursor must be in a channel higher than the primary channel
+ * - there are 4 channels in the LCD 0 and only 2 channels in the LCD 1
+ */
+static const struct {
+       u8 chan;
+       u8 layer;
+       u8 pipe;
+       u8 type;                        /* plane type */
+       const uint32_t *formats;
+       u8 n_formats;
+} plane_tb[] = {
+       [DE2_PRIMARY_PLANE] = {         /* primary plane: channel 0 (VI) */
+               0, 0, 0,
+               DRM_PLANE_TYPE_PRIMARY,
+               ui_formats, ARRAY_SIZE(ui_formats),
+       },
+       [DE2_CURSOR_PLANE] = {          /* cursor: channel 1 (UI) */
+               1, 0, 1,
+               DRM_PLANE_TYPE_CURSOR,
+               ui_formats, ARRAY_SIZE(ui_formats),
+       },
+       {
+               0, 1, 0,                /* 1st overlay: channel 0, layer 1 */
+               DRM_PLANE_TYPE_OVERLAY,
+               vi_formats, ARRAY_SIZE(vi_formats),
+       },
+       {
+               0, 2, 0,                /* 2nd overlay: channel 0, layer 2 */
+               DRM_PLANE_TYPE_OVERLAY,
+               vi_formats, ARRAY_SIZE(vi_formats),
+       },
+       {
+               0, 3, 0,                /* 3rd overlay: channel 0, layer 3 */
+               DRM_PLANE_TYPE_OVERLAY,
+               vi_formats, ARRAY_SIZE(vi_formats),
+       },
+};
+
+static inline void andl_relaxed(void __iomem *addr, u32 val)
+{
+       writel_relaxed(readl_relaxed(addr) & val, addr);
+}
+
+static inline void orl_relaxed(void __iomem *addr, u32 val)
+{
+       writel_relaxed(readl_relaxed(addr) | val, addr);
+}
+
+/* alert the DE processor about changes in a mixer configuration */
+static void de2_mixer_select(struct priv *priv,
+                       int mixer,
+                       void __iomem *mixer_io)
+{
+       /* select the mixer */
+       andl_relaxed(priv->mmio + DE2_SEL_REG, ~1);
+
+       /* double register switch */
+       writel_relaxed(1, mixer_io + MIXER_GLB_REGS + MIXER_GLB_DBUFF_REG);
+}
+
+/*
+ * cleanup a mixer
+ *
+ * This is needed only once after power on.
+ */
+static void de2_mixer_cleanup(struct priv *priv, int mixer,
+                               u32 size)
+{
+       void __iomem *mixer_io = priv->mmio;
+       void __iomem *chan_io;
+       u32 data;
+       unsigned int i;
+
+       mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+       chan_io = mixer_io + MIXER_CHAN_REGS;
+
+       andl_relaxed(priv->mmio + DE2_SEL_REG, ~1);
+       writel_relaxed(1, mixer_io + MIXER_GLB_REGS + MIXER_GLB_DBUFF_REG);
+
+       writel_relaxed(MIXER_GLB_CTL_rt_en,
+                       mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG);
+       writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG);
+
+       writel_relaxed(size, mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+
+       /*
+        * clear the VI/UI channels
+        *      LCD0: 1 VI and 3 UIs
+        *      LCD1: 1 VI and 1 UI
+        */
+       memset_io(chan_io, 0, VI_SIZE);
+       memset_io(chan_io + MIXER_CHAN_SZ, 0, UI_SIZE);
+       if (mixer == 0) {
+               memset_io(chan_io + MIXER_CHAN_SZ * 2, 0, UI_SIZE);
+               memset_io(chan_io + MIXER_CHAN_SZ * 3, 0, UI_SIZE);
+       }
+
+       /* alpha blending */
+       writel_relaxed(0x00000001 |             /* fcolor for primary */
+                       MIXER_BLD_FCOLOR_CTL_PEN(0),
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_FCOLOR_CTL_REG);
+       for (i = 0; i < MIXER_BLD_ATTR_N; i++) {
+               writel_relaxed(0xff000000,
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_FCOLOR(i));
+               writel_relaxed(size,
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_INSIZE(i));
+               writel_relaxed(0,
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_OFFSET(i));
+       }
+       writel_relaxed(0, mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG);
+
+       /* prepare the pipe route for the planes */
+       data = 0;
+       for (i = 0; i < DE2_N_PLANES; i++)
+               data |= MIXER_BLD_ROUTE(plane_tb[i].chan, plane_tb[i].pipe);
+       writel_relaxed(data, mixer_io + MIXER_BLD_REGS + MIXER_BLD_ROUTE_REG);
+
+       writel_relaxed(0, mixer_io + MIXER_BLD_REGS +
+                       MIXER_BLD_PREMULTIPLY_REG);
+       writel_relaxed(0xff000000, mixer_io + MIXER_BLD_REGS +
+                       MIXER_BLD_BKCOLOR_REG);
+       writel_relaxed(size, mixer_io + MIXER_BLD_REGS +
+                       MIXER_BLD_OUTPUT_SIZE_REG);
+       writel_relaxed(MIXER_BLD_MODE_SRCOVER,
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_MODEx_REG(0));
+       writel_relaxed(MIXER_BLD_MODE_SRCOVER,
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_MODEx_REG(1));
+
+       /* disable the enhancements */
+       writel_relaxed(0, mixer_io + MIXER_VSU_REGS);
+       writel_relaxed(0, mixer_io + MIXER_GSU1_REGS);
+       writel_relaxed(0, mixer_io + MIXER_GSU2_REGS);
+       writel_relaxed(0, mixer_io + MIXER_GSU3_REGS);
+       writel_relaxed(0, mixer_io + MIXER_FCE_REGS);
+       writel_relaxed(0, mixer_io + MIXER_BWS_REGS);
+       writel_relaxed(0, mixer_io + MIXER_LTI_REGS);
+       writel_relaxed(0, mixer_io + MIXER_PEAK_REGS);
+       writel_relaxed(0, mixer_io + MIXER_ASE_REGS);
+       writel_relaxed(0, mixer_io + MIXER_FCC_REGS);
+       writel_relaxed(0, mixer_io + MIXER_DCSC_REGS);
+}
+
+/* enable a mixer */
+static void de2_mixer_enable(struct lcd *lcd)
+{
+       struct priv *priv = lcd->priv;
+       void __iomem *mixer_io = priv->mmio;
+       struct drm_display_mode *mode = &lcd->crtc.mode;
+       u32 size = WH(mode->hdisplay, mode->vdisplay);
+       u32 data;
+       int mixer = lcd->mixer;
+       int i;
+
+       mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+       /* if not done yet, start the DE processor */
+       if (!priv->started) {
+               reset_control_deassert(priv->reset);
+               clk_prepare_enable(priv->gate);
+               clk_prepare_enable(priv->clk);
+       }
+       priv->started |= 1 << mixer;
+
+       /* set the A83T clock divider (500 / 2) = 250MHz */
+       if (priv->soc_type == SOC_A83T)
+               writel_relaxed(0x00000011, /* div = 2 for both LCDs */
+                               priv->mmio + DE2_DIV_REG);
+
+       /* deassert the mixer and enable its clock */
+       orl_relaxed(priv->mmio + DE2_RESET_REG, mixer == 0 ? 1 : 4);
+       data = 1 << mixer;                      /* 1 bit / lcd */
+       orl_relaxed(priv->mmio + DE2_GATE_REG, data);
+       orl_relaxed(priv->mmio + DE2_MOD_REG, data);
+
+       /* if not done yet, cleanup and enable */
+       if (!(priv->clean & (1 << mixer))) {
+               priv->clean |= 1 << mixer;
+               de2_mixer_cleanup(priv, mixer, size);
+               return;
+       }
+
+       /* enable */
+       de2_mixer_select(priv, mixer, mixer_io);
+
+       writel_relaxed(MIXER_GLB_CTL_rt_en,
+                       mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG);
+       writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG);
+
+       /* set the size of the frame buffer */
+       writel_relaxed(size, mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+       for (i = 0; i < MIXER_BLD_ATTR_N; i++)
+               writel_relaxed(size, mixer_io + MIXER_BLD_REGS +
+                               MIXER_BLD_ATTRx_INSIZE(i));
+       writel_relaxed(size, mixer_io + MIXER_BLD_REGS +
+                       MIXER_BLD_OUTPUT_SIZE_REG);
+
+       writel_relaxed(mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 0,
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG);
+}
+
+/* enable a LCD (DE mixer) */
+void de2_de_enable(struct lcd *lcd)
+{
+       mutex_lock(&lcd->priv->mutex);
+
+       de2_mixer_enable(lcd);
+
+       mutex_unlock(&lcd->priv->mutex);
+}
+
+/* disable a LCD (DE mixer) */
+void de2_de_disable(struct lcd *lcd)
+{
+       struct priv *priv = lcd->priv;
+       void __iomem *mixer_io = priv->mmio;
+       int mixer = lcd->mixer;
+       u32 data;
+
+       mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+       mutex_lock(&priv->mutex);
+
+       de2_mixer_select(priv, mixer, mixer_io);
+
+       writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG);
+
+       data = ~(1 << mixer);
+       andl_relaxed(priv->mmio + DE2_MOD_REG, data);
+       andl_relaxed(priv->mmio + DE2_GATE_REG, data);
+       andl_relaxed(priv->mmio + DE2_RESET_REG, data);
+
+       mutex_unlock(&priv->mutex);
+
+       /* if all mixers are disabled, stop the DE */
+       priv->started &= ~(1 << mixer);
+       if (!priv->started) {
+               clk_disable_unprepare(priv->clk);
+               clk_disable_unprepare(priv->gate);
+               reset_control_assert(priv->reset);
+       }
+}
+
+static void de2_vi_update(void __iomem *chan_io,
+                         struct drm_gem_cma_object *gem,
+                         int layer,
+                         unsigned int fmt,
+                         u32 ui_sel,
+                         u32 size,
+                         u32 coord,
+                         struct drm_framebuffer *fb,
+                         u32 screen_size)
+{
+       int i;
+
+       writel_relaxed(VI_CFG_ATTR_en |
+                       (fmt << VI_CFG_ATTR_fmt_SHIFT) |
+                       ui_sel,
+                       chan_io + VI_CFGx_ATTR(layer));
+       writel_relaxed(size, chan_io + VI_CFGx_SIZE(layer));
+       writel_relaxed(coord, chan_io + VI_CFGx_COORD(layer));
+       for (i = 0; i < VI_N_PLANES; i++) {
+               writel_relaxed(fb->pitches[i] ? fb->pitches[i] :
+                                               fb->pitches[0],
+                               chan_io + VI_CFGx_PITCHy(layer, i));
+               writel_relaxed(gem->paddr + fb->offsets[i],
+                               chan_io + VI_CFGx_TOP_LADDRy(layer, i));
+       }
+       writel_relaxed(0xff000000, chan_io + VI_FCOLORx(layer));
+       if (layer == 0) {
+               writel_relaxed(screen_size,
+                               chan_io + VI_OVL_SIZEx(0));
+       }
+}
+
+static void de2_ui_update(void __iomem *chan_io,
+                         struct drm_gem_cma_object *gem,
+                         int layer,
+                         unsigned int fmt,
+                         u32 alpha_glob,
+                         u32 size,
+                         u32 coord,
+                         struct drm_framebuffer *fb,
+                         u32 screen_size)
+{
+       writel_relaxed(UI_CFG_ATTR_en |
+                       (fmt << UI_CFG_ATTR_fmt_SHIFT) |
+                       alpha_glob,
+                       chan_io + UI_CFGx_ATTR(layer));
+       writel_relaxed(size, chan_io + UI_CFGx_SIZE(layer));
+       writel_relaxed(coord, chan_io + UI_CFGx_COORD(layer));
+       writel_relaxed(fb->pitches[0], chan_io + UI_CFGx_PITCH(layer));
+       writel_relaxed(gem->paddr + fb->offsets[0],
+                       chan_io + UI_CFGx_TOP_LADDR(layer));
+       if (layer == 0)
+               writel_relaxed(screen_size, chan_io + UI_OVL_SIZE);
+}
+
+static void de2_plane_update(struct priv *priv, struct lcd *lcd,
+                               int plane_num,
+                               struct drm_plane_state *state,
+                               struct drm_plane_state *old_state)
+{
+       void __iomem *mixer_io = priv->mmio;
+       void __iomem *chan_io;
+       struct drm_framebuffer *fb = state->fb;
+       struct drm_gem_cma_object *gem;
+       u32 size = WH(state->crtc_w, state->crtc_h);
+       u32 coord, screen_size;
+       u32 fcolor;
+       u32 ui_sel, alpha_glob;
+       int mixer = lcd->mixer;
+       int chan, layer, x, y;
+       unsigned int fmt;
+
+       chan = plane_tb[plane_num].chan;
+       layer = plane_tb[plane_num].layer;
+
+       mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+       chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan;
+
+       x = state->crtc_x >= 0 ? state->crtc_x : 0;
+       y = state->crtc_y >= 0 ? state->crtc_y : 0;
+       coord = XY(x, y);
+
+       /* if plane update was delayed, force a full update */
+       if (priv->lcds[drm_crtc_index(&lcd->crtc)]->delayed &
+                       (1 << plane_num)) {
+               priv->lcds[drm_crtc_index(&lcd->crtc)]->delayed &=
+                                                       ~(1 << plane_num);
+
+       /* handle plane move */
+       } else if (fb == old_state->fb) {
+               de2_mixer_select(priv, mixer, mixer_io);
+               if (chan == 0)
+                       writel_relaxed(coord, chan_io + VI_CFGx_COORD(layer));
+               else
+                       writel_relaxed(coord, chan_io + UI_CFGx_COORD(layer));
+               return;
+       }
+
+       gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+       ui_sel = alpha_glob = 0;
+
+       switch (fb->pixel_format) {
+       case DRM_FORMAT_ARGB8888:
+               fmt = DE2_FORMAT_ARGB_8888;
+               ui_sel = VI_CFG_ATTR_ui_sel;
+               break;
+       case DRM_FORMAT_BGRA8888:
+               fmt = DE2_FORMAT_BGRA_8888;
+               ui_sel = VI_CFG_ATTR_ui_sel;
+               break;
+       case DRM_FORMAT_XRGB8888:
+               fmt = DE2_FORMAT_XRGB_8888;
+               ui_sel = VI_CFG_ATTR_ui_sel;
+               alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) |
+                               (0xff << UI_CFG_ATTR_alpha_SHIFT);
+               break;
+       case DRM_FORMAT_RGB888:
+               fmt = DE2_FORMAT_RGB_888;
+               ui_sel = VI_CFG_ATTR_ui_sel;
+               break;
+       case DRM_FORMAT_BGR888:
+               fmt = DE2_FORMAT_BGR_888;
+               ui_sel = VI_CFG_ATTR_ui_sel;
+               break;
+       case DRM_FORMAT_YUYV:
+               fmt = DE2_FORMAT_YUV422_I_YUYV;
+               break;
+       case DRM_FORMAT_YVYU:
+               fmt = DE2_FORMAT_YUV422_I_YVYU;
+               break;
+       case DRM_FORMAT_YUV422:
+               fmt = DE2_FORMAT_YUV422_P;
+               break;
+       case DRM_FORMAT_YUV420:
+               fmt = DE2_FORMAT_YUV420_P;
+               break;
+       case DRM_FORMAT_UYVY:
+               fmt = DE2_FORMAT_YUV422_I_UYVY;
+               break;
+       default:
+               pr_err("de2_plane_update: format %.4s not yet treated\n",
+                       (char *) &fb->pixel_format);
+               return;
+       }
+
+       /* the overlay size is the one of the primary plane */
+       screen_size = plane_num == DE2_PRIMARY_PLANE ?
+               size :
+               readl_relaxed(mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+
+       /* prepare pipe enable */
+       fcolor = readl_relaxed(mixer_io + MIXER_BLD_REGS +
+                               MIXER_BLD_FCOLOR_CTL_REG);
+       fcolor |= MIXER_BLD_FCOLOR_CTL_PEN(plane_tb[plane_num].pipe);
+
+       de2_mixer_select(priv, mixer, mixer_io);
+
+       if (chan == 0)                          /* VI channel */
+               de2_vi_update(chan_io, gem, layer, fmt, ui_sel, size, coord,
+                               fb, screen_size);
+       else                                    /* UI channel */
+               de2_ui_update(chan_io, gem, layer, fmt, alpha_glob, size, coord,
+                               fb, screen_size);
+       writel_relaxed(fcolor, mixer_io + MIXER_BLD_REGS +
+                               MIXER_BLD_FCOLOR_CTL_REG);
+}
+
+static int vi_nb_layers(void __iomem *chan_io)
+{
+       int layer, n = 0;
+
+       for (layer = 0; layer < 4; layer++) {
+               if (readl_relaxed(chan_io + VI_CFGx_ATTR(layer)) != 0)
+                       n++;
+       }
+
+       return n;
+}
+
+static int ui_nb_layers(void __iomem *chan_io)
+{
+       int layer, n = 0;
+
+       for (layer = 0; layer < 4; layer++) {
+               if (readl_relaxed(chan_io + UI_CFGx_ATTR(layer)) != 0)
+                       n++;
+       }
+
+       return n;
+}
+
+static void de2_plane_disable(struct priv *priv,
+                               int mixer, int plane_num)
+{
+       void __iomem *mixer_io = priv->mmio;
+       void __iomem *chan_io;
+       u32 fcolor;
+       int chan, layer, n;
+
+       chan = plane_tb[plane_num].chan;
+       layer = plane_tb[plane_num].layer;
+
+       mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+       chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan;
+
+       if (chan == 0)
+               n = vi_nb_layers(chan_io);
+       else
+               n = ui_nb_layers(chan_io);
+
+       fcolor = readl_relaxed(mixer_io + MIXER_BLD_REGS +
+                       MIXER_BLD_FCOLOR_CTL_REG);
+
+       de2_mixer_select(priv, mixer, mixer_io);
+
+       if (chan == 0)
+               writel_relaxed(0, chan_io + VI_CFGx_ATTR(layer));
+       else
+               writel_relaxed(0, chan_io + UI_CFGx_ATTR(layer));
+
+       /* disable the pipe if no more active layer */
+       if (n <= 1)
+               writel_relaxed(fcolor &
+                       ~MIXER_BLD_FCOLOR_CTL_PEN(plane_tb[plane_num].pipe),
+                       mixer_io + MIXER_BLD_REGS + MIXER_BLD_FCOLOR_CTL_REG);
+}
+
+static void de2_drm_plane_update(struct drm_plane *plane,
+                               struct drm_plane_state *old_state)
+{
+       struct drm_plane_state *state = plane->state;
+       struct drm_crtc *crtc = state->crtc;
+       struct lcd *lcd = crtc_to_lcd(crtc);
+       struct priv *priv = lcd->priv;
+       int plane_num = plane - lcd->planes;
+
+       /* if the crtc is disabled, mark update delayed */
+       if (!(priv->started & (1 << lcd->mixer))) {
+               lcd->delayed |= 1 << plane_num;
+               return;                         /* mixer disabled */
+       }
+
+       mutex_lock(&priv->mutex);
+
+       de2_plane_update(priv, lcd, plane_num, state, old_state);
+
+       mutex_unlock(&priv->mutex);
+}
+
+static void de2_drm_plane_disable(struct drm_plane *plane,
+                               struct drm_plane_state *old_state)
+{
+       struct drm_crtc *crtc = old_state->crtc;
+       struct lcd *lcd = crtc_to_lcd(crtc);
+       struct priv *priv = lcd->priv;
+       int plane_num = plane - lcd->planes;
+
+       if (!(priv->started & (1 << lcd->mixer)))
+               return;                         /* mixer disabled */
+
+       mutex_lock(&priv->mutex);
+
+       de2_plane_disable(lcd->priv, lcd->mixer, plane_num);
+
+       mutex_unlock(&priv->mutex);
+}
+
+static const struct drm_plane_helper_funcs plane_helper_funcs = {
+       .atomic_update = de2_drm_plane_update,
+       .atomic_disable = de2_drm_plane_disable,
+};
+
+static const struct drm_plane_funcs plane_funcs = {
+       .update_plane = drm_atomic_helper_update_plane,
+       .disable_plane = drm_atomic_helper_disable_plane,
+       .destroy = drm_plane_cleanup,
+       .reset = drm_atomic_helper_plane_reset,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static int de2_one_plane_init(struct drm_device *drm,
+                               struct drm_plane *plane,
+                               int possible_crtcs,
+                               int plane_num)
+{
+       int ret;
+
+       ret = drm_universal_plane_init(drm, plane, possible_crtcs,
+                               &plane_funcs,
+                               plane_tb[plane_num].formats,
+                               plane_tb[plane_num].n_formats,
+                               plane_tb[plane_num].type, NULL);
+       if (ret >= 0)
+               drm_plane_helper_add(plane, &plane_helper_funcs);
+
+       return ret;
+}
+
+/* initialize the planes */
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd)
+{
+       int i, n, ret, possible_crtcs = 1 << drm_crtc_index(&lcd->crtc);
+
+       n = ARRAY_SIZE(plane_tb);
+       if (n != DE2_N_PLANES) {
+               dev_err(lcd->dev, "Bug: incorrect number of planes %d != "
+                       __stringify(DE2_N_PLANES) "\n", n);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < n; i++) {
+               ret = de2_one_plane_init(drm, &lcd->planes[i],
+                               possible_crtcs, i);
+               if (ret < 0) {
+                       dev_err(lcd->dev, "plane init failed %d\n", ret);
+                       break;
+               }
+       }
+
+       return ret;
+}
-- 
2.10.2

-- 
You received this message because you are subscribed to the Google Groups 
"linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to linux-sunxi+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to