* [PATCH v6 1/5] drm: sun8i: Add a basic DRM driver for Allwinner DE2
[not found] ` <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
@ 2016-11-20 9:53 ` Jean-Francois Moine
2016-11-21 16:59 ` Rob Herring
2016-11-20 9:56 ` [PATCH v6 2/5] drm: sun8i: add HDMI video support to A83T and H3 Jean-Francois Moine
` (4 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Jean-Francois Moine @ 2016-11-20 9:53 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
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 <moinejf-GANU6spQydw@public.gmane.org>
---
.../bindings/display/sunxi/sun8i-de2.txt | 83 +++
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 | 440 +++++++++++++
drivers/gpu/drm/sun8i/de2_crtc.h | 50 ++
drivers/gpu/drm/sun8i/de2_drm.h | 48 ++
drivers/gpu/drm/sun8i/de2_drv.c | 379 +++++++++++
drivers/gpu/drm/sun8i/de2_plane.c | 712 +++++++++++++++++++++
10 files changed, 1741 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
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_drm.h
create mode 100644 drivers/gpu/drm/sun8i/de2_drv.c
create mode 100644 drivers/gpu/drm/sun8i/de2_plane.c
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
new file mode 100644
index 0000000..b9edd4b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
@@ -0,0 +1,83 @@
+Allwinner sun8i Display Engine 2 subsystem
+==========================================
+
+The Allwinner DE2 subsystem contains a display controller (DE2),
+one or two LCD controllers (TCON) and their external interfaces.
+
+Display controller
+==================
+
+Required properties:
+
+- compatible: value should be one of the following
+ "allwinner,sun8i-a83t-display-engine"
+ "allwinner,sun8i-h3-display-engine"
+
+- clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+
+- clock-names: must contain
+ "gate": DE bus gate
+ "clock": DE clock
+
+- resets: phandle to the reset of the device
+
+- ports: phandle's to the LCD ports
+
+LCD controller
+==============
+
+Required properties:
+
+- compatible: should be
+ "allwinner,sun8i-a83t-tcon"
+
+- clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+
+- clock-names: must contain
+ "gate": TCON bus gate
+ "clock": TCON pixel clock
+
+- resets: phandle to the reset of the device
+
+- port: port node with endpoint definitions as defined in
+ Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+ de: de-controller@01000000 {
+ compatible = "allwinner,sun8i-h3-display-engine";
+ ...
+ clocks = <&&ccu CLK_BUS_DE>, <&ccu CLK_DE>;
+ clock-names = "gate", "clock";
+ resets = <&ccu RST_BUS_DE>;
+ ports = <&lcd0_p>;
+ };
+
+ lcd0: lcd-controller@01c0c000 {
+ compatible = "allwinner,sun8i-a83t-tcon";
+ ...
+ clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>;
+ clock-names = "gate", "clock";
+ resets = <&ccu RST_BUS_TCON0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ lcd0_p: port {
+ lcd0_ep: endpoint {
+ remote-endpoint = <&hdmi_ep>;
+ };
+ };
+ };
+
+ hdmi: hdmi@01ee0000 {
+ ...
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port {
+ hdmi_ep: endpoint {
+ remote-endpoint = <&lcd0_ep>;
+ };
+ };
+ };
+
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..65c9b93
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_crtc.c
@@ -0,0 +1,440 @@
+/*
+ * Allwinner DRM driver - DE2 CRTC
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ *
+ * 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 "de2_drm.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 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,
+ 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 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);
+
+ writel_relaxed(XY(1, 1), lcd->mmio + TCON1_PS_SYNC_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);
+
+ writel_relaxed(0x0fffffff, /* TRI disabled */
+ lcd->mmio + TCON1_IO_TRI_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;
+ struct clk *parent_clk;
+ u32 parent_rate;
+ int ret;
+
+ /* determine and set the best rate for the parent clock (pll-video) */
+ if (297000 % mode->clock == 0)
+ parent_rate = 297000000;
+ else if ((270000 * 2) % mode->clock == 0)
+ parent_rate = 270000000;
+ else
+ return; /* "640x480" rejected */
+ 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;
+ }
+
+ /* then, set the TCON clock rate */
+ ret = clk_set_rate(lcd->clk, mode->clock * 1000);
+ if (ret) {
+ dev_err(lcd->dev, "set rate %dKHz failed %d\n",
+ mode->clock, ret);
+ return;
+ }
+
+ /* start the TCON */
+ reset_control_deassert(lcd->reset);
+ clk_prepare_enable(lcd->bus);
+ clk_prepare_enable(lcd->clk);
+ lcd->clk_enabled = true;
+
+ 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, index;
+
+ lcd->priv = priv;
+
+ ret = de2_plane_init(drm, lcd);
+ if (ret < 0)
+ return ret;
+
+ drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs);
+
+ ret = drm_crtc_init_with_planes(drm, crtc,
+ &lcd->planes[DE2_PRIMARY_PLANE],
+ &lcd->planes[DE2_CURSOR_PLANE],
+ &de2_crtc_funcs, NULL);
+ if (ret)
+ return ret;
+
+ /* set the lcd/crtc reference */
+ index = drm_crtc_index(crtc);
+ if (index >= ARRAY_SIZE(priv->lcds)) {
+ dev_err(drm->dev, "Bad crtc index");
+ return -ENOENT;
+ }
+ priv->lcds[index] = lcd;
+
+ return ret;
+}
+
+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;
+
+ /* get the LCD (mixer) number */
+ id = of_alias_get_id(np, "lcd");
+ if (id < 0 || id >= 2) {
+ dev_err(dev, "no or bad alias for lcd\n");
+ id = 0;
+ }
+ 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 CRTCs */
+ 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..f663ba4
--- /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_drm.h b/drivers/gpu/drm/sun8i/de2_drm.h
new file mode 100644
index 0000000..c42c30a
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_drm.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_drv.c b/drivers/gpu/drm/sun8i/de2_drv.c
new file mode 100644
index 0000000..67368f5
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_drv.c
@@ -0,0 +1,379 @@
+/*
+ * Allwinner DRM driver - DE2 DRM driver
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ *
+ * 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 <linux/of_graph.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_drm.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;
+ int 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, "out of memory\n");
+ 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, "failed to initialize vblank\n");
+
+ {
+ struct lcd *lcd;
+ int i;
+
+ 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,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static int de2_drm_add_components(struct device *dev,
+ int (*compare_of)(struct device *, void *),
+ 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 CRTCs */
+ 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;
+ }
+
+ /* bind the encoders/connectors */
+ 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;
+ }
+ 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 de2_drm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = de2_drm_add_components(&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 <moinejf-GANU6spQydw@public.gmane.org>");
+MODULE_DESCRIPTION("Allwinner DE2 DRM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sun8i/de2_plane.c b/drivers/gpu/drm/sun8i/de2_plane.c
new file mode 100644
index 0000000..47c94dd
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_plane.c
@@ -0,0 +1,712 @@
+/*
+ * Allwinner DRM driver - Display Engine 2
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ * 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_drm.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)
+{
+ void __iomem *mixer_io = priv->mmio;
+ void __iomem *chan_io;
+ u32 size = WH(1920, 1080); /* (any size) */
+ unsigned int i;
+ u32 data;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+ chan_io = mixer_io + MIXER_CHAN_REGS;
+
+ /* 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);
+
+ de2_mixer_select(priv, mixer, mixer_io);
+ 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);
+ }
+
+ /* clear and set default values alpha blending */
+ memset_io(mixer_io + MIXER_BLD_REGS, 0,
+ MIXER_BLD_ATTR_SIZE * MIXER_BLD_ATTR_N);
+ 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));
+ writel_relaxed(0, mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG);
+
+ /* 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;
+ int mixer = lcd->mixer;
+ u32 data;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ if (priv->started & (1 << mixer))
+ return; /* mixer already enabled */
+
+ /* 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;
+
+ /* deassert the mixer and enable the 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);
+
+ /* enable */
+ andl_relaxed(priv->mmio + DE2_SEL_REG, ~1); /* mixer select */
+ writel_relaxed(MIXER_GLB_CTL_rt_en | MIXER_GLB_CTL_rtwb_port,
+ mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG);
+ writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG);
+
+ /* restore the frame buffer size */
+ writel_relaxed(WH(lcd->crtc.mode.hdisplay,
+ lcd->crtc.mode.vdisplay),
+ mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+
+ /* if not yet done, cleanup */
+ if (!(priv->clean & (1 << mixer))) {
+ priv->clean |= 1 << mixer;
+ de2_mixer_cleanup(priv, mixer);
+ }
+}
+
+/* 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;
+
+ if (!(priv->started & (1 << mixer)))
+ return; /* mixer already disabled */
+
+ 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;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ chan = plane_tb[plane_num].chan;
+ layer = plane_tb[plane_num].layer;
+
+ 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 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, chan_disable = 0;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ chan = plane_tb[plane_num].chan;
+ layer = plane_tb[plane_num].layer;
+
+ chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan;
+
+ /*
+ * check if the pipe should be disabled
+ * (this code works with only 2 layers)
+ */
+ if (chan == 0) {
+ if (readl_relaxed(chan_io + VI_CFGx_ATTR(1 - layer)) == 0)
+ chan_disable = 1;
+ } else {
+ if (readl_relaxed(chan_io + UI_CFGx_ATTR(1 - layer)) == 0)
+ chan_disable = 1;
+ }
+
+ 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));
+
+ /* if no more layer in this channel, disable the pipe */
+ if (chan_disable) {
+ 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+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v6 2/5] drm: sun8i: add HDMI video support to A83T and H3
[not found] ` <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
2016-11-20 9:53 ` [PATCH v6 1/5] drm: sun8i: Add a basic DRM driver for Allwinner DE2 Jean-Francois Moine
@ 2016-11-20 9:56 ` Jean-Francois Moine
[not found] ` <cea6a5397664213c6a917028ec2e429d25972490.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
2016-11-20 11:20 ` [PATCH v6 3/5] ARM: dts: sun8i-h3: add HDMI video nodes Jean-Francois Moine
` (3 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Jean-Francois Moine @ 2016-11-20 9:56 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
This patch adds a HDMI video driver to the Allwinner's SoCs A83T and H3.
Signed-off-by: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
---
.../devicetree/bindings/display/sunxi/hdmi.txt | 53 ++
drivers/gpu/drm/sun8i/Kconfig | 7 +
drivers/gpu/drm/sun8i/Makefile | 2 +
drivers/gpu/drm/sun8i/de2_hdmi.c | 394 ++++++++++
drivers/gpu/drm/sun8i/de2_hdmi.h | 51 ++
drivers/gpu/drm/sun8i/de2_hdmi_io.c | 839 +++++++++++++++++++++
6 files changed, 1346 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi.c
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi.h
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi_io.c
diff --git a/Documentation/devicetree/bindings/display/sunxi/hdmi.txt b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt
new file mode 100644
index 0000000..85709ab
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt
@@ -0,0 +1,53 @@
+Allwinner HDMI Transmitter
+==========================
+
+The Allwinner HDMI transmitters are included in the SoCs.
+They support audio and video.
+
+Required properties:
+ - #address-cells : should be <1>
+ - #size-cells : should be <0>
+ - compatible : should be one of
+ "allwinner,sun8i-a83t-hdmi"
+ "allwinner,sun8i-h3-hdmi"
+ - clocks : phandles to the HDMI clocks as described in
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+ - clock-names : must be
+ "gate" : bus gate
+ "clock" : streaming clock
+ "ddc-clock" : DDC clock
+ - resets : One or two phandles to the HDMI resets
+ - reset-names : when 2 phandles, must be
+ "hdmi0" and "hdmi1"
+
+Required nodes:
+ - port: Audio and video input port nodes with endpoint definitions
+ as defined in Documentation/devicetree/bindings/graph.txt.
+ port@0 is video and port@1 is audio.
+
+Example:
+
+ hdmi: hdmi@01ee0000 {
+ compatible = "allwinner,sun8i-a83t-hdmi";
+ reg = <0x01ee0000 0x20000>;
+ clocks = <&ccu CLK_BUS_HDMI>, <&ccu CLK_HDMI>,
+ <&ccu CLK_HDMI_DDC>;
+ clock-names = "gate", "clock", "ddc-clock";
+ resets = <&ccu RST_HDMI0>, <&ccu RST_HDMI1>;
+ reset-names = "hdmi0", "hdmi1";
+ ...
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 { /* video */
+ reg = <0>;
+ hdmi_lcd1: endpoint {
+ remote-endpoint = <&lcd1_hdmi>;
+ };
+ };
+ port@1 { /* audio */
+ reg = <1>;
+ hdmi_i2s2: endpoint {
+ remote-endpoint = <&i2s2_hdmi>;
+ };
+ };
+ };
diff --git a/drivers/gpu/drm/sun8i/Kconfig b/drivers/gpu/drm/sun8i/Kconfig
index 6940895..5c4607b 100644
--- a/drivers/gpu/drm/sun8i/Kconfig
+++ b/drivers/gpu/drm/sun8i/Kconfig
@@ -17,3 +17,10 @@ config DRM_SUN8I_DE2
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.
+
+config DRM_SUN8I_DE2_HDMI
+ tristate "Support for DE2 HDMI"
+ depends on DRM_SUN8I_DE2
+ help
+ Choose this option if you use want HDMI on DE2.
+ If M is selected the module will be called sun8i-de2-hdmi.
diff --git a/drivers/gpu/drm/sun8i/Makefile b/drivers/gpu/drm/sun8i/Makefile
index f107919..6ba97c2 100644
--- a/drivers/gpu/drm/sun8i/Makefile
+++ b/drivers/gpu/drm/sun8i/Makefile
@@ -3,5 +3,7 @@
#
sun8i-de2-drm-objs := de2_drv.o de2_crtc.o de2_plane.o
+sun8i-de2-hdmi-objs := de2_hdmi.o de2_hdmi_io.o
obj-$(CONFIG_DRM_SUN8I_DE2) += sun8i-de2-drm.o
+obj-$(CONFIG_DRM_SUN8I_DE2_HDMI) += sun8i-de2-hdmi.o
diff --git a/drivers/gpu/drm/sun8i/de2_hdmi.c b/drivers/gpu/drm/sun8i/de2_hdmi.c
new file mode 100644
index 0000000..9898a12
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_hdmi.c
@@ -0,0 +1,394 @@
+/*
+ * Allwinner DRM driver - HDMI
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ *
+ * 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/component.h>
+#include <linux/clk.h>
+#include <linux/hdmi.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include "de2_hdmi.h"
+
+static const struct of_device_id de2_hdmi_dt_ids[] = {
+ { .compatible = "allwinner,sun8i-a83t-hdmi",
+ .data = (void *) SOC_A83T },
+ { .compatible = "allwinner,sun8i-h3-hdmi",
+ .data = (void *) SOC_H3 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, de2_hdmi_dt_ids);
+
+#define conn_to_priv(x) \
+ container_of(x, struct de2_hdmi_priv, connector)
+
+#define enc_to_priv(x) \
+ container_of(x, struct de2_hdmi_priv, encoder)
+
+/* --- encoder functions --- */
+
+static void de2_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+ struct clk *parent_clk;
+ u32 parent_rate;
+ int ret;
+
+ priv->cea_mode = drm_match_cea_mode(mode);
+
+ DRM_DEBUG_DRIVER("cea_mode %d\n", priv->cea_mode);
+
+ /* determine and set the best rate for the parent clock (pll-video) */
+ if ((270000 * 2) % mode->clock == 0)
+ parent_rate = 270000000;
+ else if (297000 % mode->clock == 0)
+ parent_rate = 297000000;
+ else
+ return; /* "640x480" rejected */
+ parent_clk = clk_get_parent(priv->clk);
+
+ ret = clk_set_rate(parent_clk, parent_rate);
+ if (ret) {
+ dev_err(priv->dev, "set parent rate failed %d\n", ret);
+ return;
+ }
+ ret = clk_set_rate(priv->clk, mode->clock * 1000);
+ if (ret)
+ dev_err(priv->dev, "set rate failed %d\n", ret);
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_video_mode(priv, mode);
+ mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_video_on(priv);
+ mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_video_off(priv);
+ mutex_unlock(&priv->mutex);
+}
+
+static const struct drm_encoder_helper_funcs de2_hdmi_encoder_helper_funcs = {
+ .mode_set = de2_hdmi_encoder_mode_set,
+ .enable = de2_hdmi_encoder_enable,
+ .disable = de2_hdmi_encoder_disable,
+};
+
+static const struct drm_encoder_funcs de2_hdmi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+/* --- connector functions --- */
+
+static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ int cea_mode = drm_match_cea_mode(mode);
+
+ if (hdmi_io_mode_valid(cea_mode) < 0)
+ return MODE_NOMODE;
+
+ return MODE_OK;
+}
+
+static enum drm_connector_status de2_hdmi_connector_detect(
+ struct drm_connector *connector, bool force)
+{
+ struct de2_hdmi_priv *priv = conn_to_priv(connector);
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = hdmi_io_get_hpd(priv);
+ mutex_unlock(&priv->mutex);
+
+ return ret ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int read_edid_block(void *data, u8 *buf,
+ unsigned int blk, size_t length)
+{
+ struct de2_hdmi_priv *priv = data;
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = hdmi_io_ddc_read(priv,
+ blk / 2, (blk & 1) ? 128 : 0,
+ length, buf);
+ mutex_unlock(&priv->mutex);
+
+ return ret;
+}
+
+static int de2_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ struct de2_hdmi_priv *priv = conn_to_priv(connector);
+ struct edid *edid;
+ int n;
+
+ edid = drm_do_get_edid(connector, read_edid_block, priv);
+
+ if (!edid) {
+ dev_warn(priv->dev, "failed to read EDID\n");
+ if (!connector->cmdline_mode.specified)
+ return 0;
+
+ return drm_add_modes_noedid(connector,
+ connector->cmdline_mode.xres,
+ connector->cmdline_mode.yres);
+ }
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ n = drm_add_edid_modes(connector, edid);
+
+ drm_edid_to_eld(connector, edid);
+
+ kfree(edid);
+
+ DRM_DEBUG_DRIVER("%s EDID ok %d modes\n",
+ connector->eld[0] ? "HDMI" : "DVI", n);
+
+ return n;
+}
+
+static const
+struct drm_connector_helper_funcs de2_hdmi_connector_helper_funcs = {
+ .get_modes = de2_hdmi_connector_get_modes,
+ .mode_valid = de2_hdmi_connector_mode_valid,
+};
+
+static const struct drm_connector_funcs de2_hdmi_connector_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = de2_hdmi_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static void de2_hdmi_cleanup(struct de2_hdmi_priv *priv)
+{
+ clk_disable_unprepare(priv->clk_ddc);
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->gate);
+ reset_control_assert(priv->reset1);
+ reset_control_assert(priv->reset0);
+}
+
+static int de2_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &priv->encoder;
+ struct drm_connector *connector = &priv->connector;
+ int ret;
+
+ encoder->possible_crtcs =
+ drm_of_find_possible_crtcs(drm, dev->of_node);
+
+ /* if no CRTC, delay */
+ if (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ /* HDMI init */
+ ret = reset_control_deassert(priv->reset0);
+ if (ret)
+ goto err;
+ ret = reset_control_deassert(priv->reset1);
+ if (ret)
+ goto err;
+ ret = clk_prepare_enable(priv->gate);
+ if (ret)
+ goto err;
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ goto err;
+ ret = clk_prepare_enable(priv->clk_ddc);
+ if (ret)
+ goto err;
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_init(priv);
+ mutex_unlock(&priv->mutex);
+
+ /* encoder init */
+ ret = drm_encoder_init(drm, encoder, &de2_hdmi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret)
+ goto err;
+
+ drm_encoder_helper_add(encoder, &de2_hdmi_encoder_helper_funcs);
+
+ /* connector init */
+ ret = drm_connector_init(drm, connector,
+ &de2_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret)
+ goto err_connector;
+
+ connector->interlace_allowed = 1;
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ drm_connector_helper_add(connector,
+ &de2_hdmi_connector_helper_funcs);
+
+ drm_mode_connector_attach_encoder(connector, encoder);
+
+ return 0;
+
+err_connector:
+ drm_encoder_cleanup(encoder);
+err:
+ dev_err(dev, "err %d\n", ret);
+ return ret;
+}
+
+static void de2_hdmi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->connector.dev)
+ drm_connector_cleanup(&priv->connector);
+ drm_encoder_cleanup(&priv->encoder);
+ de2_hdmi_cleanup(priv);
+}
+
+static const struct component_ops de2_hdmi_ops = {
+ .bind = de2_hdmi_bind,
+ .unbind = de2_hdmi_unbind,
+};
+
+static int de2_hdmi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct de2_hdmi_priv *priv;
+ struct resource *res;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ mutex_init(&priv->mutex);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "failed to get memory resource\n");
+ return -ENXIO;
+ }
+ priv->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ dev_err(dev, "failed to map registers err %d\n", ret);
+ return ret;
+ }
+
+ priv->gate = devm_clk_get(dev, "bus");
+ if (IS_ERR(priv->gate)) {
+ ret = PTR_ERR(priv->gate);
+ dev_err(dev, "gate clock err %d\n", ret);
+ return ret;
+ }
+
+ priv->clk = devm_clk_get(dev, "clock");
+ if (IS_ERR(priv->clk)) {
+ ret = PTR_ERR(priv->clk);
+ dev_err(dev, "hdmi clock err %d\n", ret);
+ return ret;
+ }
+
+ priv->clk_ddc = devm_clk_get(dev, "ddc-clock");
+ if (IS_ERR(priv->clk_ddc)) {
+ ret = PTR_ERR(priv->clk_ddc);
+ dev_err(dev, "hdmi-ddc clock err %d\n", ret);
+ return ret;
+ }
+
+ priv->reset0 = devm_reset_control_get(dev, "hdmi0");
+ if (IS_ERR(priv->reset0)) {
+ ret = PTR_ERR(priv->reset0);
+ dev_err(dev, "reset controller err %d\n", ret);
+ return ret;
+ }
+
+ priv->reset1 = devm_reset_control_get(dev, "hdmi1");
+ if (IS_ERR(priv->reset1)) {
+ ret = PTR_ERR(priv->reset1);
+ dev_err(dev, "reset controller err %d\n", ret);
+ return ret;
+ }
+
+ priv->soc_type = (int) of_match_device(de2_hdmi_dt_ids,
+ &pdev->dev)->data;
+
+ return component_add(dev, &de2_hdmi_ops);
+}
+
+static int de2_hdmi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &de2_hdmi_ops);
+
+ return 0;
+}
+
+static struct platform_driver de2_hdmi_driver = {
+ .probe = de2_hdmi_probe,
+ .remove = de2_hdmi_remove,
+ .driver = {
+ .name = "sun8i-de2-hdmi",
+ .of_match_table = of_match_ptr(de2_hdmi_dt_ids),
+ },
+};
+
+/* create the video HDMI driver */
+static int __init de2_hdmi_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&de2_hdmi_driver);
+
+ return ret;
+}
+
+static void __exit de2_hdmi_fini(void)
+{
+ platform_driver_unregister(&de2_hdmi_driver);
+}
+
+module_init(de2_hdmi_init);
+module_exit(de2_hdmi_fini);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>");
+MODULE_DESCRIPTION("Allwinner DE2 HDMI encoder/connector");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sun8i/de2_hdmi.h b/drivers/gpu/drm/sun8i/de2_hdmi.h
new file mode 100644
index 0000000..1f0b1d9
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_hdmi.h
@@ -0,0 +1,51 @@
+#ifndef __DE2_HDMI_H__
+#define __DE2_HDMI_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 <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drmP.h>
+
+/* SoC types */
+#define SOC_A83T 0
+#define SOC_H3 1
+
+struct de2_hdmi_priv {
+ struct device *dev;
+ void __iomem *mmio;
+
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+
+ struct clk *clk;
+ struct clk *clk_ddc;
+ struct clk *gate;
+ struct reset_control *reset0;
+ struct reset_control *reset1;
+
+ struct mutex mutex;
+ u8 soc_type;
+ u8 cea_mode;
+};
+
+/* in de2_hdmi_io.c */
+void hdmi_io_init(struct de2_hdmi_priv *priv);
+void hdmi_io_video_on(struct de2_hdmi_priv *priv);
+void hdmi_io_video_off(struct de2_hdmi_priv *priv);
+int hdmi_io_video_mode(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode);
+int hdmi_io_ddc_read(struct de2_hdmi_priv *priv,
+ char pointer, char offset,
+ int nbyte, char *pbuf);
+int hdmi_io_get_hpd(struct de2_hdmi_priv *priv);
+int hdmi_io_mode_valid(int cea_mode);
+
+#endif /* __DE2_HDMI_H__ */
diff --git a/drivers/gpu/drm/sun8i/de2_hdmi_io.c b/drivers/gpu/drm/sun8i/de2_hdmi_io.c
new file mode 100644
index 0000000..8c690bf
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_hdmi_io.c
@@ -0,0 +1,839 @@
+/*
+ * Allwinner A83T and H3 HDMI lowlevel functions
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ * 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.
+ */
+
+/*
+ * The HDMI controller in the A83T and H3 seems to be a
+ * Synopsys DesignWare HDMI controller.
+ * The PHYs are unknown.
+ * Documentation:
+ * https://linux-sunxi.org/DWC_HDMI_Controller
+ * https://www.synopsys.com/dw/doc.php/ds/c/dwc_hdmi_tx_csds.pdf
+ */
+
+#include <linux/hdmi.h>
+
+#include "de2_hdmi.h"
+
+/* guessed PHY registers */
+#define HDMI_PHY_LOCK_READ_REG 0x10010
+#define HDMI_PHY_CTRL_REG 0x10020
+#define HDMI_PHY_24_REG 0x10024
+#define HDMI_PHY_28_REG 0x10028
+#define HDMI_PHY_PLL_REG 0x1002c
+#define HDMI_PHY_CLK_REG 0x10030
+#define HDMI_PHY_34_REG 0x10034
+#define HDMI_PHY_STATUS_REG 0x10038
+
+/* DW registers (obfuscated addresses) */
+
+/* Interrupt Registers */
+#define R_0100_HDMI_IH_FC_STAT0 0x0010
+#define R_0101_HDMI_IH_FC_STAT1 0x0011
+#define R_0102_HDMI_IH_FC_STAT2 0x8010
+#define R_0103_HDMI_IH_AS_STAT0 0x8011
+#define R_0104_HDMI_IH_PHY_STAT0 0x0012
+#define R_0105_HDMI_IH_I2CM_STAT0 0x0013
+#define R_0106_HDMI_IH_CEC_STAT0 0x8012
+#define R_0107_HDMI_IH_VP_STAT0 0x8013
+#define R_0108_HDMI_IH_I2CMPHY_STAT0 0x4010
+#define R_01ff_HDMI_IH_MUTE 0xf01f
+
+/* Video Sample Registers */
+#define R_0200_HDMI_TX_INVID0 0x0800
+#define R_0201_HDMI_TX_INSTUFFING 0x0801
+#define R_0202_HDMI_TX_GYDATA0 0x8800
+#define R_0203_HDMI_TX_GYDATA1 0x8801
+#define R_0204_HDMI_TX_RCRDATA0 0x0802
+#define R_0205_HDMI_TX_RCRDATA1 0x0803
+#define R_0206_HDMI_TX_BCBDATA0 0x8802
+#define R_0207_HDMI_TX_BCBDATA1 0x8803
+
+/* Video Packetizer Registers */
+#define R_0801_HDMI_VP_PR_CD 0x0401
+#define R_0802_HDMI_VP_STUFF 0x8400
+#define R_0803_HDMI_VP_REMAP 0x8401
+#define R_0804_HDMI_VP_CONF 0x0402
+#define R_0807_HDMI_VP_MASK 0x8403
+
+/* Frame Composer Registers */
+#define R_1000_HDMI_FC_INVIDCONF 0x0040
+#define HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH 0x10
+#define R_1001_HDMI_FC_INHACTV0 0x0041
+#define R_1002_HDMI_FC_INHACTV1 0x8040
+#define R_1003_HDMI_FC_INHBLANK0 0x8041
+#define R_1004_HDMI_FC_INHBLANK1 0x0042
+#define R_1005_HDMI_FC_INVACTV0 0x0043
+#define R_1006_HDMI_FC_INVACTV1 0x8042
+#define R_1007_HDMI_FC_INVBLANK 0x8043
+#define R_1008_HDMI_FC_HSYNCINDELAY0 0x4040
+#define R_1009_HDMI_FC_HSYNCINDELAY1 0x4041
+#define R_100a_HDMI_FC_HSYNCINWIDTH0 0xc040
+#define R_100b_HDMI_FC_HSYNCINWIDTH1 0xc041
+#define R_100c_HDMI_FC_VSYNCINDELAY 0x4042
+#define R_100d_HDMI_FC_VSYNCINWIDTH 0x4043
+#define R_1011_HDMI_FC_CTRLDUR 0x0045
+#define R_1012_HDMI_FC_EXCTRLDUR 0x8044
+#define R_1013_HDMI_FC_EXCTRLSPAC 0x8045
+#define R_1014_HDMI_FC_CH0PREAM 0x0046
+#define R_1015_HDMI_FC_CH1PREAM 0x0047
+#define R_1016_HDMI_FC_CH2PREAM 0x8046
+#define R_1018_HDMI_FC_GCP 0x4044
+#define R_1019_HDMI_FC_AVICONF0 0x4045
+#define HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN 0x20
+#define R_101a_HDMI_FC_AVICONF1 0xc044
+#define R_101b_HDMI_FC_AVICONF2 0xc045
+#define R_101c_HDMI_FC_AVIVID 0x4046
+#define R_1025_HDMI_FC_AUDICONF0 0x2043
+#define R_1026_HDMI_FC_AUDICONF1 0xa042
+#define R_1027_HDMI_FC_AUDICONF2 0xa043
+#define R_1028_HDMI_FC_AUDICONF3 0x6040
+#define R_1029_HDMI_FC_VSDIEEEID0 0x6041
+#define R_1030_HDMI_FC_VSDIEEEID1 0x2044
+#define R_1031_HDMI_FC_VSDIEEEID2 0x2045
+#define R_1032_HDMI_FC_VSDPAYLOAD0 0xa044
+#define R_1033_HDMI_FC_VSDPAYLOAD1 0xa045
+#define R_1034_HDMI_FC_VSDPAYLOAD2 0x2046
+#define R_1063_HDMI_FC_AUDSCONF 0xa049
+#define R_1065_HDMI_FC_AUDSV 0x204b
+#define R_1066_HDMI_FC_AUDSU 0xa04a
+#define R_1067_HDMI_FC_AUDSCHNLS0 0xa04b
+#define HDMI_FC_AUDSCHNLS0_CGMSA 0x30
+#define R_1068_HDMI_FC_AUDSCHNLS1 0x6048
+#define R_1069_HDMI_FC_AUDSCHNLS2 0x6049
+#define R_106a_HDMI_FC_AUDSCHNLS3 0xe048
+#define HDMI_FC_AUDSCHNLS3_OIEC_CH0(v) (v)
+#define HDMI_FC_AUDSCHNLS3_OIEC_CH1(v) (v << 4)
+#define R_106b_HDMI_FC_AUDSCHNLS4 0xe049
+#define HDMI_FC_AUDSCHNLS4_OIEC_CH2(v) (v)
+#define HDMI_FC_AUDSCHNLS4_OIEC_CH3(v) (v << 4)
+#define R_106c_HDMI_FC_AUDSCHNLS5 0x604a
+#define HDMI_FC_AUDSCHNLS5_OIEC_CH0(v) (v)
+#define HDMI_FC_AUDSCHNLS5_OIEC_CH1(v) (v << 4)
+#define R_106d_HDMI_FC_AUDSCHNLS6 0x604b
+#define HDMI_FC_AUDSCHNLS6_OIEC_CH2(v) (v)
+#define HDMI_FC_AUDSCHNLS6_OIEC_CH3(v) (v << 4)
+#define R_106e_HDMI_FC_AUDSCHNLS7 0xe04a
+#define R_106f_HDMI_FC_AUDSCHNLS8 0xe04b
+#define HDMI_FC_AUDSCHNLS8_WORDLENGTH(v) (v)
+#define R_10b3_HDMI_FC_DATAUTO0 0xb045
+#define R_10b4_HDMI_FC_DATAUTO1 0x3046
+#define R_10b5_HDMI_FC_DATAUTO2 0x3047
+#define R_10d2_HDMI_FC_MASK0 0x904c
+#define R_10d6_HDMI_FC_MASK1 0x904e
+#define R_10da_HDMI_FC_MASK2 0xd04c
+#define R_10e0_HDMI_FC_PRCONF 0x3048
+#define R_1103_HDMI_FC_GMD_CONF 0x8051
+#define R_1104_HDMI_FC_GMD_HB 0x0052
+#define R_1200_HDMI_FC_DBGFORCE 0x0840
+#define HDMI_FC_DBGFORCE_FORCEAUDIO BIT(4)
+#define HDMI_FC_DBGFORCE_FORCEVIDEO BIT(0)
+#define R_1219_HDMI_FC_DBGTMDS0 0x4845
+
+/* HDMI Source PHY Registers */
+#define R_3000_HDMI_PHY_CONF0 0x0240
+#define HDMI_PHY_CONF0_PDZ BIT(7)
+#define HDMI_PHY_CONF0_ENTMDS BIT(6)
+#define HDMI_PHY_CONF0_SPARECTRL BIT(5)
+#define HDMI_PHY_CONF0_GEN2_PDDQ BIT(4)
+#define HDMI_PHY_CONF0_GEN2_TXPWRON BIT(3)
+#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE BIT(2)
+#define HDMI_PHY_CONF0_SELDATAENPOL BIT(1)
+#define HDMI_PHY_CONF0_SELDIPIF BIT(0)
+#define R_3001_HDMI_PHY_TST0 0x0241
+#define HDMI_PHY_TST0_TSTCLR BIT(5)
+#define R_3005_HDMI_PHY_INT0 0x0243
+#define R_3006_HDMI_PHY_MASK0 0x8242
+
+/* HDMI Master PHY Registers */
+#define R_3020_HDMI_PHY_I2CM_SLAVE_ADDR 0x2240
+#define HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 0x69
+#define R_3021_HDMI_PHY_I2CM_ADDRESS_ADDR 0x2241
+#define R_3022_HDMI_PHY_I2CM_DATAO_1_ADDR 0xa240
+#define R_3023_HDMI_PHY_I2CM_DATAO_0_ADDR 0xa241
+#define R_3026_HDMI_PHY_I2CM_OPERATION_ADDR 0xa242
+#define HDMI_PHY_I2CM_OPERATION_ADDR_WRITE 0x10
+#define R_3027_HDMI_PHY_I2CM_INT_ADDR 0xa243
+#define R_3028_HDMI_PHY_I2CM_CTLINT_ADDR 0x6240
+
+/* Audio Sampler Registers */
+#define R_3100_HDMI_AUD_CONF0 0x0250
+#define HDMI_AUD_CONF0_SW_RESET 0x80
+#define HDMI_AUD_CONF0_I2S_ALL_ENABLE 0x2f
+#define R_3101_HDMI_AUD_CONF1 0x0251
+#define R_3102_HDMI_AUD_INT 0x8250
+#define R_3103_HDMI_AUD_CONF2 0x8251
+#define R_3200_HDMI_AUD_N1 0x0a40
+#define R_3201_HDMI_AUD_N2 0x0a41
+#define R_3202_HDMI_AUD_N3 0x8a40
+#define R_3205_HDMI_AUD_CTS3 0x0a43
+#define R_3206_HDMI_AUD_INPUTCLKFS 0x8a42
+#define HDMI_AUD_INPUTCLKFS_64FS 0x04
+#define R_3302_HDMI_AUD_SPDIFINT 0x8a50
+
+/* Generic Parallel Audio Interface Registers */
+#define R_3506_HDMI_GP_POL 0x8272
+
+/* Main Controller Registers */
+#define R_4001_HDMI_MC_CLKDIS 0x0081
+#define HDMI_MC_CLKDIS_HDCPCLK_DISABLE BIT(6)
+#define HDMI_MC_CLKDIS_AUDCLK_DISABLE BIT(3)
+#define HDMI_MC_CLKDIS_TMDSCLK_DISABLE BIT(1)
+#define R_4002_HDMI_MC_SWRSTZ 0x8080
+#define R_4004_HDMI_MC_FLOWCTRL 0x0082
+#define R_4005_HDMI_MC_PHYRSTZ 0x0083
+#define HDMI_MC_PHYRSTZ_DEASSERT BIT(0)
+
+/* HDCP Encryption Engine Registers */
+#define R_5000_HDMI_A_HDCPCFG0 0x00c0
+#define R_5001_HDMI_A_HDCPCFG1 0x00c1
+#define HDMI_A_HDCPCFG1_PH2UPSHFTENC BIT(2)
+#define HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE BIT(1)
+#define HDMI_A_HDCPCFG1_SWRESET BIT(0)
+#define R_5008_HDMI_A_APIINTMSK 0x40c0
+#define R_5009_HDMI_A_VIDPOLCFG 0x40c1
+#define HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH BIT(4)
+
+/* CEC Engine Registers */
+#define R_7d02_HDMI_CEC_MASK 0x86f0
+
+/* I2C Master Registers (E-DDC) */
+#define R_7e00_HDMI_I2CM_SLAVE 0x0ee0
+#define R_7e01_HDMI_I2CM_ADDRESS 0x0ee1
+#define R_7e03_HDMI_I2CM_DATAI 0x8ee1
+#define R_7e04_HDMI_I2CM_OPERATION 0x0ee2
+#define HDMI_I2CM_OPERATION_DDC_READ 0x02
+#define R_7e05_HDMI_I2CM_INT 0x0ee3
+#define R_7e06_HDMI_I2CM_CTLINT 0x8ee2
+#define R_7e07_HDMI_I2CM_DIV 0x8ee3
+#define R_7e08_HDMI_I2CM_SEGADDR 0x4ee0
+#define R_7e09_HDMI_I2CM_SOFTRSTZ 0x4ee1
+#define R_7e0a_HDMI_I2CM_SEGPTR 0xcee0
+#define R_7e0b_HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0xcee1
+#define R_7e0c_HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x4ee2
+#define R_7e0d_HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x4ee3
+#define R_7e0e_HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0xcee2
+#define R_7e0f_HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0xcee3
+#define R_7e10_HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x0ee4
+#define R_7e11_HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x0ee5
+
+#define VIC_720x480_60 2
+#define VIC_1280x720_60 4
+#define VIC_1920x1080i_60 5
+#define VIC_720x480i_60 6
+#define VIC_1920x1080_60 16
+#define VIC_720x576_50 17
+#define VIC_1280x720_50 19
+#define VIC_1920x1080i_50 20
+#define VIC_720x576i_50 21
+#define VIC_1920x1080_50 31
+#define VIC_1920x1080_24 32
+#define VIC_1920x1080_25 33
+#define VIC_1920x1080_30 34
+
+static inline u8 hdmi_readb(struct de2_hdmi_priv *priv, u32 addr)
+{
+ return readb_relaxed(priv->mmio + addr);
+}
+
+static inline u32 hdmi_readl(struct de2_hdmi_priv *priv, u32 addr)
+{
+ return readl_relaxed(priv->mmio + addr);
+}
+
+static inline void hdmi_writeb(struct de2_hdmi_priv *priv, u32 addr, u8 data)
+{
+ writeb_relaxed(data, priv->mmio + addr);
+}
+
+static inline void hdmi_writel(struct de2_hdmi_priv *priv, u32 addr, u32 data)
+{
+ writel_relaxed(data, priv->mmio + addr);
+}
+
+static inline void hdmi_orb(struct de2_hdmi_priv *priv, u32 addr, u8 data)
+{
+ writeb_relaxed(readb_relaxed(priv->mmio + addr) | data,
+ priv->mmio + addr);
+}
+
+static inline void hdmi_orl(struct de2_hdmi_priv *priv, u32 addr, u32 data)
+{
+ writel_relaxed(readl_relaxed(priv->mmio + addr) | data,
+ priv->mmio + addr);
+}
+
+static inline void hdmi_andl(struct de2_hdmi_priv *priv, u32 addr, u32 data)
+{
+ writel_relaxed(readl_relaxed(priv->mmio + addr) & data,
+ priv->mmio + addr);
+}
+
+/* read lock/unlock functions */
+static inline void hdmi_lock_read(struct de2_hdmi_priv *priv)
+{
+ hdmi_writel(priv, HDMI_PHY_LOCK_READ_REG, 0x54524545);
+}
+static inline void hdmi_unlock_read(struct de2_hdmi_priv *priv)
+{
+ hdmi_writel(priv, HDMI_PHY_LOCK_READ_REG, 0x57415452);
+}
+
+static void hdmi_inner_init(struct de2_hdmi_priv *priv)
+{
+ u8 clkdis = priv->soc_type == SOC_H3 ?
+ ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE : 0xff;
+
+ hdmi_lock_read(priv);
+
+ /* software reset */
+ hdmi_writeb(priv, R_4002_HDMI_MC_SWRSTZ, 0x00);
+ udelay(2);
+
+ /* mask all interrupts */
+ hdmi_writeb(priv, R_01ff_HDMI_IH_MUTE, 0x00);
+ hdmi_writeb(priv, R_0807_HDMI_VP_MASK, 0xff);
+ hdmi_writeb(priv, R_10d2_HDMI_FC_MASK0, 0xff);
+ hdmi_writeb(priv, R_10d6_HDMI_FC_MASK1, 0xff);
+ hdmi_writeb(priv, R_10da_HDMI_FC_MASK2, 0xff);
+ hdmi_writeb(priv, R_3102_HDMI_AUD_INT, 0xff);
+ hdmi_writeb(priv, R_3302_HDMI_AUD_SPDIFINT, 0xff);
+ hdmi_writeb(priv, R_3506_HDMI_GP_POL, 0xff);
+ hdmi_writeb(priv, R_5008_HDMI_A_APIINTMSK, 0xff);
+ hdmi_writeb(priv, R_7d02_HDMI_CEC_MASK, 0xff);
+ hdmi_writeb(priv, R_7e05_HDMI_I2CM_INT, 0xff);
+ hdmi_writeb(priv, R_7e06_HDMI_I2CM_CTLINT, 0xff);
+
+ hdmi_writeb(priv, R_1063_HDMI_FC_AUDSCONF, 0xf0);
+ hdmi_writeb(priv, R_10b3_HDMI_FC_DATAUTO0, 0x1e);
+ hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1, 0x00);
+ hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1,
+ HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE |
+ HDMI_A_HDCPCFG1_SWRESET);
+ hdmi_writeb(priv, R_5000_HDMI_A_HDCPCFG0, 0x00);
+ hdmi_writeb(priv, R_5009_HDMI_A_VIDPOLCFG,
+ HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, clkdis);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, clkdis);
+ hdmi_writeb(priv, R_0100_HDMI_IH_FC_STAT0, 0xff);
+ hdmi_writeb(priv, R_0101_HDMI_IH_FC_STAT1, 0xff);
+ hdmi_writeb(priv, R_0102_HDMI_IH_FC_STAT2, 0xff);
+ hdmi_writeb(priv, R_0103_HDMI_IH_AS_STAT0, 0xff);
+ hdmi_writeb(priv, R_0105_HDMI_IH_I2CM_STAT0, 0xff);
+ hdmi_writeb(priv, R_0106_HDMI_IH_CEC_STAT0, 0xff);
+ hdmi_writeb(priv, R_0107_HDMI_IH_VP_STAT0, 0xff);
+}
+
+static void hdmi_phy_init_a83t(struct de2_hdmi_priv *priv)
+{
+ hdmi_inner_init(priv);
+
+ hdmi_writeb(priv, 0x10000, 0x01);
+ hdmi_writeb(priv, 0x10001, 0x00);
+ hdmi_writeb(priv, 0x10002, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
+ hdmi_writeb(priv, 0x10003, 0x00);
+ hdmi_writeb(priv, 0x10007, 0xa0);
+ hdmi_writeb(priv, R_4005_HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_DEASSERT);
+ udelay(1);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ |
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3006_HDMI_PHY_MASK0, 0xf0);
+ hdmi_writeb(priv, R_3027_HDMI_PHY_I2CM_INT_ADDR, 0xff);
+ hdmi_writeb(priv, R_3028_HDMI_PHY_I2CM_CTLINT_ADDR, 0xff);
+ hdmi_writeb(priv, R_0104_HDMI_IH_PHY_STAT0, 0xff);
+ hdmi_writeb(priv, R_0108_HDMI_IH_I2CMPHY_STAT0, 0xff);
+ hdmi_writeb(priv, R_4005_HDMI_MC_PHYRSTZ, 0x00);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ |
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3001_HDMI_PHY_TST0, HDMI_PHY_TST0_TSTCLR);
+ hdmi_writeb(priv, R_3020_HDMI_PHY_I2CM_SLAVE_ADDR,
+ HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
+ hdmi_writeb(priv, R_3001_HDMI_PHY_TST0, 0x00);
+}
+
+static void hdmi_phy_init_h3(struct de2_hdmi_priv *priv)
+{
+ int to_cnt;
+ u32 tmp;
+
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 1 << 0);
+ udelay(5);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 16);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 1);
+ udelay(10);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 2);
+ udelay(5);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 3);
+ usleep_range(40, 50);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 19);
+ usleep_range(100, 120);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 18);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 7 << 4);
+
+ to_cnt = 10;
+ while (1) {
+ if (hdmi_readl(priv, HDMI_PHY_STATUS_REG) & 0x80)
+ break;
+ usleep_range(200, 250);
+ if (--to_cnt == 0) {
+ pr_warn("hdmi phy init timeout\n");
+ break;
+ }
+ }
+
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 0xf << 8);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 7);
+
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x80084343);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = hdmi_readl(priv, HDMI_PHY_STATUS_REG);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, (tmp >> 11) & 0x3f);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ff0f7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x80639000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c405);
+
+ hdmi_inner_init(priv);
+}
+
+static void hdmi_i2cm_write(struct de2_hdmi_priv *priv,
+ int addr, u8 valh, u8 vall)
+{
+ hdmi_writeb(priv, R_3021_HDMI_PHY_I2CM_ADDRESS_ADDR, addr);
+ hdmi_writeb(priv, R_3022_HDMI_PHY_I2CM_DATAO_1_ADDR, valh);
+ hdmi_writeb(priv, R_3023_HDMI_PHY_I2CM_DATAO_0_ADDR, vall);
+ hdmi_writeb(priv, R_3026_HDMI_PHY_I2CM_OPERATION_ADDR,
+ HDMI_PHY_I2CM_OPERATION_ADDR_WRITE);
+ usleep_range(2000, 2500);
+}
+
+static int get_divider(int rate)
+{
+ if (rate <= 27000)
+ return 11;
+ if (rate <= 74250)
+ return 4;
+ if (rate <= 148500)
+ return 2;
+ return 1;
+}
+
+static void hdmi_phy_set_a83t(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode)
+{
+ switch (get_divider(mode->clock)) {
+ case 1:
+ hdmi_i2cm_write(priv, 0x06, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x0f);
+ hdmi_i2cm_write(priv, 0x10, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x02);
+ hdmi_i2cm_write(priv, 0x0e, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x2b);
+ break;
+ case 2: /* 1080P @ 60 & 50 */
+ hdmi_i2cm_write(priv, 0x06, 0x04, 0xa0);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x0a);
+ hdmi_i2cm_write(priv, 0x10, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x02);
+ hdmi_i2cm_write(priv, 0x0e, 0x00, 0x21);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x29);
+ break;
+ case 4: /* 720P @ 50 & 60, 1080I, 1080P */
+ hdmi_i2cm_write(priv, 0x06, 0x05, 0x40);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x05);
+ hdmi_i2cm_write(priv, 0x10, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x07);
+ hdmi_i2cm_write(priv, 0x0e, 0x02, 0xb5);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x09);
+ break;
+/* case 11: * 480P/576P */
+ default:
+ hdmi_i2cm_write(priv, 0x06, 0x01,
+ mode->flags & DRM_MODE_FLAG_DBLCLK ? 0xe3 : 0xe0);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x10, 0x08, 0xda);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x07);
+ hdmi_i2cm_write(priv, 0x0e, 0x03, 0x18);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x09);
+ break;
+ }
+ hdmi_i2cm_write(priv, 0x1e, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x13, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x17, 0x00, 0x00);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_TXPWRON |
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+}
+
+static void hdmi_phy_set_h3(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode)
+{
+ u32 tmp;
+
+ hdmi_andl(priv, HDMI_PHY_CTRL_REG, ~0xf000);
+
+ switch (get_divider(mode->clock)) {
+ case 1:
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x31dc5fc0);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x800863c0);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(200);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ if (tmp < 0x3d)
+ tmp += 2;
+ else
+ tmp = 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ msleep(100);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063b000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f8246b5);
+ break;
+ case 2: /* 1080P @ 60 & 50 */
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x80084381);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063a800);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c485);
+ break;
+ case 4: /* 720P @ 50 & 60, 1080I, 1080P */
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x80084343);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063b000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c405);
+ break;
+ default:
+/* case 11: * 480P/576P */
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x8008430a);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063b000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c405);
+ break;
+ }
+}
+
+/* HDMI functions */
+
+void hdmi_io_init(struct de2_hdmi_priv *priv)
+{
+ if (priv->soc_type == SOC_H3)
+ hdmi_phy_init_h3(priv);
+ else
+ hdmi_phy_init_a83t(priv);
+
+ /* hpd reset */
+ hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1,
+ HDMI_A_HDCPCFG1_PH2UPSHFTENC);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS,
+ HDMI_MC_CLKDIS_HDCPCLK_DISABLE);
+}
+
+void hdmi_io_video_on(struct de2_hdmi_priv *priv)
+{
+ if (priv->soc_type == SOC_H3)
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 0x0f << 12);
+}
+
+void hdmi_io_video_off(struct de2_hdmi_priv *priv)
+{
+ if (priv->soc_type == SOC_H3)
+ hdmi_andl(priv, HDMI_PHY_CTRL_REG, ~(0x0f << 12));
+}
+
+/* video init */
+int hdmi_io_video_mode(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode)
+{
+ int avi_d2; /* AVI InfoFrame Data Byte 2 */
+ int h_blank, v_blank, h_sync_w, h_front_p;
+ int invidconf;
+
+ /* colorimetry and aspect ratio */
+ switch (priv->cea_mode) {
+ case VIC_720x480_60:
+ case VIC_720x480i_60:
+ case VIC_720x576_50:
+ case VIC_720x576i_50:
+ avi_d2 = (HDMI_COLORIMETRY_ITU_601 << 6) |
+ (HDMI_PICTURE_ASPECT_4_3 << 4) | 0x08;
+ break;
+ default:
+ avi_d2 = (HDMI_COLORIMETRY_ITU_709 << 6) |
+ (HDMI_PICTURE_ASPECT_16_9 << 4) | 0x08;
+ break;
+ }
+
+ h_blank = mode->htotal - mode->hdisplay;
+ v_blank = mode->vtotal - mode->vdisplay;
+ h_sync_w = mode->hsync_end - mode->hsync_start;
+ h_front_p = mode->hsync_start - mode->hdisplay;
+
+ invidconf = 0;
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ invidconf |= 0x01;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ invidconf |= 0x20;
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ invidconf |= 0x40;
+
+ if (priv->soc_type == SOC_H3) {
+ hdmi_phy_set_h3(priv, mode);
+ hdmi_inner_init(priv);
+ } else {
+ hdmi_io_init(priv);
+ }
+
+ hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE,
+ HDMI_FC_DBGFORCE_FORCEVIDEO);
+ hdmi_writeb(priv, R_1219_HDMI_FC_DBGTMDS0, 0x00);
+ hdmi_writeb(priv, R_1000_HDMI_FC_INVIDCONF,
+ invidconf |
+ HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH);
+ hdmi_writeb(priv, 0x10001,
+ invidconf < 0x60 ? 0x03 : 0x00);
+ hdmi_writeb(priv, R_1002_HDMI_FC_INHACTV1,
+ mode->hdisplay >> 8);
+ hdmi_writeb(priv, R_100d_HDMI_FC_VSYNCINWIDTH,
+ mode->vsync_end - mode->vsync_start);
+ hdmi_writeb(priv, R_1006_HDMI_FC_INVACTV1,
+ mode->vdisplay >> 8);
+ hdmi_writeb(priv, R_1004_HDMI_FC_INHBLANK1,
+ h_blank >> 8);
+ hdmi_writeb(priv, R_100c_HDMI_FC_VSYNCINDELAY,
+ mode->vsync_start - mode->vdisplay);
+ hdmi_writeb(priv, R_1009_HDMI_FC_HSYNCINDELAY1,
+ h_front_p >> 8);
+ hdmi_writeb(priv, R_100b_HDMI_FC_HSYNCINWIDTH1,
+ h_sync_w >> 8);
+ hdmi_writeb(priv, R_1001_HDMI_FC_INHACTV0,
+ mode->hdisplay);
+ hdmi_writeb(priv, R_1003_HDMI_FC_INHBLANK0,
+ h_blank);
+ hdmi_writeb(priv, R_1008_HDMI_FC_HSYNCINDELAY0,
+ h_front_p);
+ hdmi_writeb(priv, R_100a_HDMI_FC_HSYNCINWIDTH0,
+ h_sync_w);
+ hdmi_writeb(priv, R_1005_HDMI_FC_INVACTV0,
+ mode->vdisplay);
+ hdmi_writeb(priv, R_1007_HDMI_FC_INVBLANK,
+ v_blank);
+ hdmi_writeb(priv, R_1011_HDMI_FC_CTRLDUR, 12);
+ hdmi_writeb(priv, R_1012_HDMI_FC_EXCTRLDUR, 32);
+ hdmi_writeb(priv, R_1013_HDMI_FC_EXCTRLSPAC, 1);
+ hdmi_writeb(priv, R_1014_HDMI_FC_CH0PREAM, 0x0b);
+ hdmi_writeb(priv, R_1015_HDMI_FC_CH1PREAM, 0x16);
+ hdmi_writeb(priv, R_1016_HDMI_FC_CH2PREAM, 0x21);
+ hdmi_writeb(priv, R_10e0_HDMI_FC_PRCONF,
+ mode->flags & DRM_MODE_FLAG_DBLCLK ? 0x21 : 0x10);
+ hdmi_writeb(priv, R_0801_HDMI_VP_PR_CD,
+ mode->flags & DRM_MODE_FLAG_DBLCLK ? 0x41 : 0x40);
+ hdmi_writeb(priv, R_0802_HDMI_VP_STUFF, 0x07);
+ hdmi_writeb(priv, R_0803_HDMI_VP_REMAP, 0x00);
+ hdmi_writeb(priv, R_0804_HDMI_VP_CONF, 0x47);
+ hdmi_writeb(priv, R_0200_HDMI_TX_INVID0, 0x01);
+ hdmi_writeb(priv, R_0201_HDMI_TX_INSTUFFING, 0x07);
+ hdmi_writeb(priv, R_0202_HDMI_TX_GYDATA0, 0x00);
+ hdmi_writeb(priv, R_0203_HDMI_TX_GYDATA1, 0x00);
+ hdmi_writeb(priv, R_0204_HDMI_TX_RCRDATA0, 0x00);
+ hdmi_writeb(priv, R_0205_HDMI_TX_RCRDATA1, 0x00);
+ hdmi_writeb(priv, R_0206_HDMI_TX_BCBDATA0, 0x00);
+ hdmi_writeb(priv, R_0207_HDMI_TX_BCBDATA1, 0x00);
+
+ if (priv->connector.eld[0]) { /* if audio/HDMI */
+ hdmi_writeb(priv, R_10b3_HDMI_FC_DATAUTO0, 0x08);
+ hdmi_writeb(priv, R_1031_HDMI_FC_VSDIEEEID2, 0x00);
+ hdmi_writeb(priv, R_1030_HDMI_FC_VSDIEEEID1,
+ HDMI_IEEE_OUI >> 8);
+ hdmi_writeb(priv, R_1029_HDMI_FC_VSDIEEEID0,
+ HDMI_IEEE_OUI & 0xff);
+ hdmi_writeb(priv, R_1032_HDMI_FC_VSDPAYLOAD0, 0x00);
+ hdmi_writeb(priv, R_1033_HDMI_FC_VSDPAYLOAD1, 0x00);
+ hdmi_writeb(priv, R_1034_HDMI_FC_VSDPAYLOAD2, 0x00);
+ hdmi_writeb(priv, R_10b4_HDMI_FC_DATAUTO1, 0x01);
+ hdmi_writeb(priv, R_10b5_HDMI_FC_DATAUTO2, 0x11);
+ hdmi_writeb(priv, R_1018_HDMI_FC_GCP, 0x00);
+ hdmi_writeb(priv, R_1104_HDMI_FC_GMD_HB, 0x00);
+ hdmi_writeb(priv, R_1103_HDMI_FC_GMD_CONF, 0x11);
+
+ hdmi_lock_read(priv);
+ hdmi_orb(priv, R_1000_HDMI_FC_INVIDCONF, 0x08);
+ hdmi_unlock_read(priv);
+
+ /* AVI */
+ hdmi_writeb(priv, R_1019_HDMI_FC_AVICONF0,
+ HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN);
+ hdmi_writeb(priv, R_101a_HDMI_FC_AVICONF1, avi_d2);
+
+ hdmi_writeb(priv, R_101b_HDMI_FC_AVICONF2, 0x08);
+ hdmi_writeb(priv, R_101c_HDMI_FC_AVIVID, priv->cea_mode);
+ }
+
+ hdmi_writeb(priv, R_4004_HDMI_MC_FLOWCTRL, 0x00);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00); /* enable all clocks */
+
+ if (priv->soc_type != SOC_H3)
+ hdmi_phy_set_a83t(priv, mode);
+
+ hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE, 0x00);
+
+ return 0;
+}
+
+/* get a block of EDID */
+int hdmi_io_ddc_read(struct de2_hdmi_priv *priv,
+ char pointer, char off,
+ int nbyte, char *pbuf)
+{
+ unsigned int to_cnt;
+ u8 reg;
+ int ret = 0;
+
+ hdmi_lock_read(priv);
+ hdmi_writeb(priv, R_7e09_HDMI_I2CM_SOFTRSTZ, 0x00);
+ to_cnt = 50;
+ while (!(hdmi_readb(priv, R_7e09_HDMI_I2CM_SOFTRSTZ) & 0x01)) {
+ udelay(10);
+ if (--to_cnt == 0) { /* wait for 500us for timeout */
+ pr_warn("hdmi ddc reset timeout\n");
+ break;
+ }
+ }
+
+ hdmi_writeb(priv, R_7e07_HDMI_I2CM_DIV, 0x05);
+ hdmi_writeb(priv, R_7e05_HDMI_I2CM_INT, 0x08);
+ hdmi_writeb(priv, R_7e0c_HDMI_I2CM_SS_SCL_HCNT_0_ADDR, 0xd8);
+ hdmi_writeb(priv, R_7e0e_HDMI_I2CM_SS_SCL_LCNT_0_ADDR, 0xfe);
+
+ while (nbyte > 0) {
+ hdmi_writeb(priv, R_7e00_HDMI_I2CM_SLAVE, 0xa0 >> 1);
+ hdmi_writeb(priv, R_7e01_HDMI_I2CM_ADDRESS, off);
+ hdmi_writeb(priv, R_7e08_HDMI_I2CM_SEGADDR, 0x60 >> 1);
+ hdmi_writeb(priv, R_7e0a_HDMI_I2CM_SEGPTR, pointer);
+ hdmi_writeb(priv, R_7e04_HDMI_I2CM_OPERATION,
+ HDMI_I2CM_OPERATION_DDC_READ);
+
+ to_cnt = 200; /* timeout 100ms */
+ while (1) {
+ reg = hdmi_readb(priv, R_0105_HDMI_IH_I2CM_STAT0);
+ hdmi_writeb(priv, R_0105_HDMI_IH_I2CM_STAT0, reg);
+ if (reg & 0x02) {
+ *pbuf++ = hdmi_readb(priv,
+ R_7e03_HDMI_I2CM_DATAI);
+ break;
+ }
+ if (reg & 0x01) {
+ pr_warn("hdmi ddc read error\n");
+ ret = -1;
+ break;
+ }
+ if (--to_cnt == 0) {
+ if (!ret) {
+ pr_warn("hdmi ddc read timeout\n");
+ ret = -1;
+ }
+ break;
+ }
+ usleep_range(500, 800);
+ }
+ if (ret)
+ break;
+ nbyte--;
+ off++;
+ }
+ hdmi_unlock_read(priv);
+
+ return ret;
+}
+
+int hdmi_io_get_hpd(struct de2_hdmi_priv *priv)
+{
+ int ret;
+
+ hdmi_lock_read(priv);
+
+ if (priv->soc_type == SOC_H3)
+ ret = hdmi_readl(priv, HDMI_PHY_STATUS_REG) & 0x80000;
+ else
+ ret = hdmi_readb(priv, R_3005_HDMI_PHY_INT0) & 0x02;
+
+ hdmi_unlock_read(priv);
+
+ return ret != 0;
+}
+
+int hdmi_io_mode_valid(int cea_mode)
+{
+ /* check the known working resolutions */
+ switch (cea_mode) {
+ case VIC_720x480_60:
+ case VIC_1280x720_60:
+ case VIC_1920x1080i_60:
+ case VIC_720x480i_60:
+ case VIC_1920x1080_60:
+ case VIC_720x576_50:
+ case VIC_1280x720_50:
+ case VIC_1920x1080i_50:
+ case VIC_720x576i_50:
+ case VIC_1920x1080_50:
+ case VIC_1920x1080_24:
+ case VIC_1920x1080_25:
+ case VIC_1920x1080_30:
+ return 1;
+ }
+ return -1;
+}
--
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+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 18+ messages in thread