[PATCH v3] drm/mcde: Add new driver for ST-Ericsson MCDE

Linus Walleij linus.walleij at linaro.org
Wed May 22 07:21:53 UTC 2019


This adds a new DRM driver for the ST-Ericsson Multi Channel
Display Engine, MCDE display controller.

This hardware has three independent DSI hosts and can composit
and display several memory buffers onto an LCD display. It
was developed for several years inside of ST-Ericsson and
shipped with a few million mobile phones from Sony and Samsung,
as well as with the Snowball community development board.

The driver is currently pretty rudimentary but supports a
simple framebuffer so we can get penguins and graphics when
using these SoCs.

Signed-off-by: Linus Walleij <linus.walleij at linaro.org>
---
ChangeLog v2->v3:
- Break out functions for enabling and disabling the
  display pipeline, use abstract enums to indicate
  FIFOs, channels, external sources, formatters and
  overlays.
- Break out functions to configure channel, FIFO, external
  sources, formatter and overlays.  This makes it more clear
  how the display pipeline is actually set up.
- Split out the registers to separate include files for the
  mcde_display.c and mcde_dsi.c driver files. Keep the few
  registers in mcde_drv.c locally.
- Switch event handling and display update in the .update()
  callback so that we arm the vblank IRQ before we
  send the display update, so there is no risk of missing
  the vblank IRQ.
- Use drm_gem_fb_simple_display_pipe_prepare_fb()
- Use  drm_gem_fb_create_with_dirty() and we start to get
  better explicit commits of the dirty framebuffers,
  with the core enabling and using the vblank, interrupt
  count going up.
- Get rid of the helper variables "enabled" and "vblank_irq_on"
  that were competing with atomic updates. Instead call the
  drm_atomic_helper_commit_tail_rpm() from the mode config
  helpers .atomic_commit_tail() so that the CRTC is always
  enabled before sending an update and voila it magically
  works.
- Order includes alphabetically.
- Get rid of <drm/drmP.h>
- Move to using an embedded struct drm_device and using
  drm_dev_init()
ChangeLog v1->v2:
- Replace the quirky custom encoder structure and hacks in
  the FBDEV core with a straight-forward internal DSI bridge
  which is in turn connected to the panel bridge. It works
  like a charm, much to my surprise.
- Drop the panel port graph parsing for the DSI host
  to panels: the panels are children of the DSI node,
  so just spin over the children and take the first
  working panel.
- Move clock lookup and handling for the DSI HS and
  LP clocks to the DSI driver file as this is now
  cleanly associated with respective DSI port.
---
 Documentation/gpu/drivers.rst            |    1 +
 Documentation/gpu/mcde.rst               |    8 +
 MAINTAINERS                              |    7 +
 drivers/gpu/drm/Kconfig                  |    2 +
 drivers/gpu/drm/Makefile                 |    1 +
 drivers/gpu/drm/mcde/Kconfig             |   18 +
 drivers/gpu/drm/mcde/Makefile            |    3 +
 drivers/gpu/drm/mcde/mcde_display.c      | 1145 ++++++++++++++++++++++
 drivers/gpu/drm/mcde/mcde_display_regs.h |  518 ++++++++++
 drivers/gpu/drm/mcde/mcde_drm.h          |   44 +
 drivers/gpu/drm/mcde/mcde_drv.c          |  572 +++++++++++
 drivers/gpu/drm/mcde/mcde_dsi.c          | 1044 ++++++++++++++++++++
 drivers/gpu/drm/mcde/mcde_dsi_regs.h     |  385 ++++++++
 13 files changed, 3748 insertions(+)
 create mode 100644 Documentation/gpu/mcde.rst
 create mode 100644 drivers/gpu/drm/mcde/Kconfig
 create mode 100644 drivers/gpu/drm/mcde/Makefile
 create mode 100644 drivers/gpu/drm/mcde/mcde_display.c
 create mode 100644 drivers/gpu/drm/mcde/mcde_display_regs.h
 create mode 100644 drivers/gpu/drm/mcde/mcde_drm.h
 create mode 100644 drivers/gpu/drm/mcde/mcde_drv.c
 create mode 100644 drivers/gpu/drm/mcde/mcde_dsi.c
 create mode 100644 drivers/gpu/drm/mcde/mcde_dsi_regs.h

diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index 044a7025477c..4bfb7068e9f7 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -7,6 +7,7 @@ GPU Driver Documentation
    amdgpu
    amdgpu-dc
    i915
+   mcde
    meson
    pl111
    tegra
diff --git a/Documentation/gpu/mcde.rst b/Documentation/gpu/mcde.rst
new file mode 100644
index 000000000000..c69e977defda
--- /dev/null
+++ b/Documentation/gpu/mcde.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=======================================================
+ drm/mcde ST-Ericsson MCDE Multi-channel display engine
+=======================================================
+
+.. kernel-doc:: drivers/gpu/drm/mcde/mcde_drv.c
+   :doc: ST-Ericsson MCDE DRM Driver
diff --git a/MAINTAINERS b/MAINTAINERS
index 5cfbea4ce575..56cbc8780507 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5128,6 +5128,13 @@ S:	Maintained
 F:	drivers/gpu/drm/tinydrm/st7735r.c
 F:	Documentation/devicetree/bindings/display/sitronix,st7735r.txt
 
+DRM DRIVER FOR ST-ERICSSON MCDE
+M:	Linus Walleij <linus.walleij at linaro.org>
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+S:	Maintained
+F:	drivers/gpu/drm/mcde/
+F:	Documentation/devicetree/bindings/display/ste,mcde.txt
+
 DRM DRIVER FOR TDFX VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/tdfx/
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index e360a4a131e1..0fe1d59e4479 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -342,6 +342,8 @@ source "drivers/gpu/drm/panfrost/Kconfig"
 
 source "drivers/gpu/drm/aspeed/Kconfig"
 
+source "drivers/gpu/drm/mcde/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 72f5036d9bfa..15e4841fe4cd 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -113,3 +113,4 @@ obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/
 obj-$(CONFIG_DRM_LIMA)  += lima/
 obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
+obj-$(CONFIG_DRM_MCDE) += mcde/
diff --git a/drivers/gpu/drm/mcde/Kconfig b/drivers/gpu/drm/mcde/Kconfig
new file mode 100644
index 000000000000..b3990126562c
--- /dev/null
+++ b/drivers/gpu/drm/mcde/Kconfig
@@ -0,0 +1,18 @@
+config DRM_MCDE
+	tristate "DRM Support for ST-Ericsson MCDE (Multichannel Display Engine)"
+	depends on DRM
+	depends on CMA
+	depends on ARM || COMPILE_TEST
+	depends on OF
+	select MFD_SYSCON
+	select DRM_MIPI_DSI
+	select DRM_BRIDGE
+	select DRM_PANEL_BRIDGE
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+	help
+	  Choose this option for DRM support for the ST-Ericsson MCDE
+	  Multi-Channel Display Engine.
+	  If M is selected the module will be called mcde_drm.
diff --git a/drivers/gpu/drm/mcde/Makefile b/drivers/gpu/drm/mcde/Makefile
new file mode 100644
index 000000000000..fe28f4e0fe46
--- /dev/null
+++ b/drivers/gpu/drm/mcde/Makefile
@@ -0,0 +1,3 @@
+mcde_drm-y +=	mcde_drv.o mcde_dsi.o mcde_display.o
+
+obj-$(CONFIG_DRM_MCDE) += mcde_drm.o
diff --git a/drivers/gpu/drm/mcde/mcde_display.c b/drivers/gpu/drm/mcde/mcde_display.c
new file mode 100644
index 000000000000..f31221a23012
--- /dev/null
+++ b/drivers/gpu/drm/mcde/mcde_display.c
@@ -0,0 +1,1145 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Linus Walleij <linus.walleij at linaro.org>
+ * Parts of this file were based on the MCDE driver by Marcus Lorentzon
+ * (C) ST-Ericsson SA 2013
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+#include <video/mipi_display.h>
+
+#include "mcde_drm.h"
+#include "mcde_display_regs.h"
+
+enum mcde_fifo {
+	MCDE_FIFO_A,
+	MCDE_FIFO_B,
+	/* TODO: implement FIFO C0 and FIFO C1 */
+};
+
+enum mcde_channel {
+	MCDE_CHANNEL_0 = 0,
+	MCDE_CHANNEL_1,
+	MCDE_CHANNEL_2,
+	MCDE_CHANNEL_3,
+};
+
+enum mcde_extsrc {
+	MCDE_EXTSRC_0 = 0,
+	MCDE_EXTSRC_1,
+	MCDE_EXTSRC_2,
+	MCDE_EXTSRC_3,
+	MCDE_EXTSRC_4,
+	MCDE_EXTSRC_5,
+	MCDE_EXTSRC_6,
+	MCDE_EXTSRC_7,
+	MCDE_EXTSRC_8,
+	MCDE_EXTSRC_9,
+};
+
+enum mcde_overlay {
+	MCDE_OVERLAY_0 = 0,
+	MCDE_OVERLAY_1,
+	MCDE_OVERLAY_2,
+	MCDE_OVERLAY_3,
+	MCDE_OVERLAY_4,
+	MCDE_OVERLAY_5,
+};
+
+enum mcde_dsi_formatter {
+	MCDE_DSI_FORMATTER_0 = 0,
+	MCDE_DSI_FORMATTER_1,
+	MCDE_DSI_FORMATTER_2,
+};
+
+void mcde_display_irq(struct mcde *mcde)
+{
+	u32 mispp, misovl, mischnl;
+	bool vblank;
+
+	/* Handle display IRQs */
+	mispp = readl(mcde->regs + MCDE_MISPP);
+	misovl = readl(mcde->regs + MCDE_MISOVL);
+	mischnl = readl(mcde->regs + MCDE_MISCHNL);
+
+	/*
+	 * Handle IRQs from the DSI link. All IRQs from the DSI links
+	 * are just latched onto the MCDE IRQ line, so we need to traverse
+	 * any active DSI masters and check if an IRQ is originating from
+	 * them.
+	 *
+	 * TODO: Currently only one DSI link is supported.
+	 */
+	if (mcde_dsi_irq(mcde->mdsi)) {
+		u32 val;
+
+		/*
+		 * In oneshot mode we do not send continuous updates
+		 * to the display, instead we only push out updates when
+		 * the update function is called, then we disable the
+		 * flow on the channel once we get the TE IRQ.
+		 */
+		if (mcde->oneshot_mode) {
+			spin_lock(&mcde->flow_lock);
+			if (--mcde->flow_active == 0) {
+				dev_dbg(mcde->dev, "TE0 IRQ\n");
+				/* Disable FIFO A flow */
+				val = readl(mcde->regs + MCDE_CRA0);
+				val &= ~MCDE_CRX0_FLOEN;
+				writel(val, mcde->regs + MCDE_CRA0);
+			}
+			spin_unlock(&mcde->flow_lock);
+		}
+	}
+
+	/* Vblank from one of the channels */
+	if (mispp & MCDE_PP_VCMPA) {
+		dev_dbg(mcde->dev, "chnl A vblank IRQ\n");
+		vblank = true;
+	}
+	if (mispp & MCDE_PP_VCMPB) {
+		dev_dbg(mcde->dev, "chnl B vblank IRQ\n");
+		vblank = true;
+	}
+	if (mispp & MCDE_PP_VCMPC0)
+		dev_dbg(mcde->dev, "chnl C0 vblank IRQ\n");
+	if (mispp & MCDE_PP_VCMPC1)
+		dev_dbg(mcde->dev, "chnl C1 vblank IRQ\n");
+	if (mispp & MCDE_PP_VSCC0)
+		dev_dbg(mcde->dev, "chnl C0 TE IRQ\n");
+	if (mispp & MCDE_PP_VSCC1)
+		dev_dbg(mcde->dev, "chnl C1 TE IRQ\n");
+	writel(mispp, mcde->regs + MCDE_RISPP);
+
+	if (vblank)
+		drm_crtc_handle_vblank(&mcde->pipe.crtc);
+
+	if (misovl)
+		dev_info(mcde->dev, "some stray overlay IRQ %08x\n", misovl);
+	writel(misovl, mcde->regs + MCDE_RISOVL);
+
+	if (mischnl)
+		dev_info(mcde->dev, "some stray channel error IRQ %08x\n",
+			 mischnl);
+	writel(mischnl, mcde->regs + MCDE_RISCHNL);
+}
+
+void mcde_display_disable_irqs(struct mcde *mcde)
+{
+	/* Disable all IRQs */
+	writel(0, mcde->regs + MCDE_IMSCPP);
+	writel(0, mcde->regs + MCDE_IMSCOVL);
+	writel(0, mcde->regs + MCDE_IMSCCHNL);
+
+	/* Clear any pending IRQs */
+	writel(0xFFFFFFFF, mcde->regs + MCDE_RISPP);
+	writel(0xFFFFFFFF, mcde->regs + MCDE_RISOVL);
+	writel(0xFFFFFFFF, mcde->regs + MCDE_RISCHNL);
+}
+
+static int mcde_display_check(struct drm_simple_display_pipe *pipe,
+			       struct drm_plane_state *pstate,
+			       struct drm_crtc_state *cstate)
+{
+	const struct drm_display_mode *mode = &cstate->mode;
+	struct drm_framebuffer *old_fb = pipe->plane.state->fb;
+	struct drm_framebuffer *fb = pstate->fb;
+
+	if (fb) {
+		u32 offset = drm_fb_cma_get_gem_addr(fb, pstate, 0);
+
+		/* FB base address must be dword aligned. */
+		if (offset & 3) {
+			DRM_DEBUG_KMS("FB not 32-bit aligned\n");
+			return -EINVAL;
+		}
+
+		/*
+		 * There's no pitch register, the mode's hdisplay
+		 * controls this.
+		 */
+		if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0]) {
+			DRM_DEBUG_KMS("can't handle pitches\n");
+			return -EINVAL;
+		}
+
+		/*
+		 * We can't change the FB format in a flicker-free
+		 * manner (and only update it during CRTC enable).
+		 */
+		if (old_fb && old_fb->format != fb->format)
+			cstate->mode_changed = true;
+	}
+
+	return 0;
+}
+
+static int mcde_configure_extsrc(struct mcde *mcde, enum mcde_extsrc src,
+				 u32 format)
+{
+	u32 val;
+	u32 conf;
+	u32 cr;
+
+	switch (src) {
+	case MCDE_EXTSRC_0:
+		conf = MCDE_EXTSRC0CONF;
+		cr = MCDE_EXTSRC0CR;
+		break;
+	case MCDE_EXTSRC_1:
+		conf = MCDE_EXTSRC1CONF;
+		cr = MCDE_EXTSRC1CR;
+		break;
+	case MCDE_EXTSRC_2:
+		conf = MCDE_EXTSRC2CONF;
+		cr = MCDE_EXTSRC2CR;
+		break;
+	case MCDE_EXTSRC_3:
+		conf = MCDE_EXTSRC3CONF;
+		cr = MCDE_EXTSRC3CR;
+		break;
+	case MCDE_EXTSRC_4:
+		conf = MCDE_EXTSRC4CONF;
+		cr = MCDE_EXTSRC4CR;
+		break;
+	case MCDE_EXTSRC_5:
+		conf = MCDE_EXTSRC5CONF;
+		cr = MCDE_EXTSRC5CR;
+		break;
+	case MCDE_EXTSRC_6:
+		conf = MCDE_EXTSRC6CONF;
+		cr = MCDE_EXTSRC6CR;
+		break;
+	case MCDE_EXTSRC_7:
+		conf = MCDE_EXTSRC7CONF;
+		cr = MCDE_EXTSRC7CR;
+		break;
+	case MCDE_EXTSRC_8:
+		conf = MCDE_EXTSRC8CONF;
+		cr = MCDE_EXTSRC8CR;
+		break;
+	case MCDE_EXTSRC_9:
+		conf = MCDE_EXTSRC9CONF;
+		cr = MCDE_EXTSRC9CR;
+		break;
+	};
+
+	/*
+	 * Configure external source 0 one buffer (buffer 0)
+	 * primary overlay ID 0.
+	 * From mcde_hw.c ovly_update_registers() in the vendor tree
+	 */
+	val = 0 << MCDE_EXTSRCXCONF_BUF_ID_SHIFT;
+	val |= 1 << MCDE_EXTSRCXCONF_BUF_NB_SHIFT;
+	val |= 0 << MCDE_EXTSRCXCONF_PRI_OVLID_SHIFT;
+	/*
+	 * MCDE has inverse semantics from DRM on RBG/BGR which is why
+	 * all the modes are inversed here.
+	 */
+	switch (format) {
+	case DRM_FORMAT_ARGB8888:
+		val |= MCDE_EXTSRCXCONF_BPP_ARGB8888 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_ABGR8888:
+		val |= MCDE_EXTSRCXCONF_BPP_ARGB8888 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		val |= MCDE_EXTSRCXCONF_BPP_XRGB8888 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_XBGR8888:
+		val |= MCDE_EXTSRCXCONF_BPP_XRGB8888 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_RGB888:
+		val |= MCDE_EXTSRCXCONF_BPP_RGB888 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_BGR888:
+		val |= MCDE_EXTSRCXCONF_BPP_RGB888 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_ARGB4444:
+		val |= MCDE_EXTSRCXCONF_BPP_ARGB4444 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_ABGR4444:
+		val |= MCDE_EXTSRCXCONF_BPP_ARGB4444 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_XRGB4444:
+		val |= MCDE_EXTSRCXCONF_BPP_RGB444 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_XBGR4444:
+		val |= MCDE_EXTSRCXCONF_BPP_RGB444 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_XRGB1555:
+		val |= MCDE_EXTSRCXCONF_BPP_IRGB1555 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_XBGR1555:
+		val |= MCDE_EXTSRCXCONF_BPP_IRGB1555 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_RGB565:
+		val |= MCDE_EXTSRCXCONF_BPP_RGB565 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		val |= MCDE_EXTSRCXCONF_BGR;
+		break;
+	case DRM_FORMAT_BGR565:
+		val |= MCDE_EXTSRCXCONF_BPP_RGB565 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	case DRM_FORMAT_YUV422:
+		val |= MCDE_EXTSRCXCONF_BPP_YCBCR422 <<
+			MCDE_EXTSRCXCONF_BPP_SHIFT;
+		break;
+	default:
+		dev_err(mcde->dev, "Unknown pixel format 0x%08x\n",
+			format);
+		return -EINVAL;
+	}
+	writel(val, mcde->regs + conf);
+
+	/* Software select, primary */
+	val = MCDE_EXTSRCXCR_SEL_MOD_SOFTWARE_SEL;
+	val |= MCDE_EXTSRCXCR_MULTIOVL_CTRL_PRIMARY;
+	writel(val, mcde->regs + cr);
+
+	return 0;
+}
+
+static void mcde_configure_overlay(struct mcde *mcde, enum mcde_overlay ovl,
+				   enum mcde_extsrc src,
+				   enum mcde_channel ch,
+				   const struct drm_display_mode *mode,
+				   u32 format)
+{
+	u32 val;
+	u32 conf1;
+	u32 conf2;
+	u32 crop;
+	u32 ljinc;
+	u32 cr;
+	u32 comp;
+
+	switch (ovl) {
+	case MCDE_OVERLAY_0:
+		conf1 = MCDE_OVL0CONF;
+		conf2 = MCDE_OVL0CONF2;
+		crop = MCDE_OVL0CROP;
+		ljinc = MCDE_OVL0LJINC;
+		cr = MCDE_OVL0CR;
+		comp = MCDE_OVL0COMP;
+		break;
+	case MCDE_OVERLAY_1:
+		conf1 = MCDE_OVL1CONF;
+		conf2 = MCDE_OVL1CONF2;
+		crop = MCDE_OVL1CROP;
+		ljinc = MCDE_OVL1LJINC;
+		cr = MCDE_OVL1CR;
+		comp = MCDE_OVL1COMP;
+		break;
+	case MCDE_OVERLAY_2:
+		conf1 = MCDE_OVL2CONF;
+		conf2 = MCDE_OVL2CONF2;
+		crop = MCDE_OVL2CROP;
+		ljinc = MCDE_OVL2LJINC;
+		cr = MCDE_OVL2CR;
+		comp = MCDE_OVL2COMP;
+		break;
+	case MCDE_OVERLAY_3:
+		conf1 = MCDE_OVL3CONF;
+		conf2 = MCDE_OVL3CONF2;
+		crop = MCDE_OVL3CROP;
+		ljinc = MCDE_OVL3LJINC;
+		cr = MCDE_OVL3CR;
+		comp = MCDE_OVL3COMP;
+		break;
+	case MCDE_OVERLAY_4:
+		conf1 = MCDE_OVL4CONF;
+		conf2 = MCDE_OVL4CONF2;
+		crop = MCDE_OVL4CROP;
+		ljinc = MCDE_OVL4LJINC;
+		cr = MCDE_OVL4CR;
+		comp = MCDE_OVL4COMP;
+		break;
+	case MCDE_OVERLAY_5:
+		conf1 = MCDE_OVL5CONF;
+		conf2 = MCDE_OVL5CONF2;
+		crop = MCDE_OVL5CROP;
+		ljinc = MCDE_OVL5LJINC;
+		cr = MCDE_OVL5CR;
+		comp = MCDE_OVL5COMP;
+		break;
+	};
+
+	val = mode->hdisplay << MCDE_OVLXCONF_PPL_SHIFT;
+	val |= mode->vdisplay << MCDE_OVLXCONF_LPF_SHIFT;
+	/* Use external source 0 that we just configured */
+	val |= src << MCDE_OVLXCONF_EXTSRC_ID_SHIFT;
+	writel(val, mcde->regs + conf1);
+
+	val = MCDE_OVLXCONF2_BP_PER_PIXEL_ALPHA;
+	val |= 0xff << MCDE_OVLXCONF2_ALPHAVALUE_SHIFT;
+	/* OPQ: overlay is opaque */
+	switch (format) {
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_ARGB4444:
+	case DRM_FORMAT_ABGR4444:
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_XBGR1555:
+		/* No OPQ */
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_YUV422:
+		val |= MCDE_OVLXCONF2_OPQ;
+		break;
+	default:
+		dev_err(mcde->dev, "Unknown pixel format 0x%08x\n",
+			format);
+		break;
+	}
+	/* The default watermark level for overlay 0 is 48 */
+	val |= 48 << MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_SHIFT;
+	writel(val, mcde->regs + conf2);
+
+	/* Number of bytes to fetch per line */
+	writel(mcde->stride, mcde->regs + ljinc);
+	/* No cropping */
+	writel(0, mcde->regs + crop);
+
+	/* Set up overlay control register */
+	val = MCDE_OVLXCR_OVLEN;
+	val |= MCDE_OVLXCR_COLCCTRL_DISABLED;
+	val |= MCDE_OVLXCR_BURSTSIZE_8W <<
+		MCDE_OVLXCR_BURSTSIZE_SHIFT;
+	val |= MCDE_OVLXCR_MAXOUTSTANDING_8_REQ <<
+		MCDE_OVLXCR_MAXOUTSTANDING_SHIFT;
+	/* Not using rotation but set it up anyways */
+	val |= MCDE_OVLXCR_ROTBURSTSIZE_8W <<
+		MCDE_OVLXCR_ROTBURSTSIZE_SHIFT;
+	writel(val, mcde->regs + cr);
+
+	/*
+	 * Set up the overlay compositor to route the overlay out to
+	 * the desired channel
+	 */
+	val = ch << MCDE_OVLXCOMP_CH_ID_SHIFT;
+	writel(val, mcde->regs + comp);
+}
+
+static void mcde_configure_channel(struct mcde *mcde, enum mcde_channel ch,
+				   enum mcde_fifo fifo,
+				   const struct drm_display_mode *mode)
+{
+	u32 val;
+	u32 conf;
+	u32 sync;
+	u32 stat;
+	u32 bgcol;
+	u32 mux;
+
+	switch (ch) {
+	case MCDE_CHANNEL_0:
+		conf = MCDE_CHNL0CONF;
+		sync = MCDE_CHNL0SYNCHMOD;
+		stat = MCDE_CHNL0STAT;
+		bgcol = MCDE_CHNL0BCKGNDCOL;
+		mux = MCDE_CHNL0MUXING;
+		break;
+	case MCDE_CHANNEL_1:
+		conf = MCDE_CHNL1CONF;
+		sync = MCDE_CHNL1SYNCHMOD;
+		stat = MCDE_CHNL1STAT;
+		bgcol = MCDE_CHNL1BCKGNDCOL;
+		mux = MCDE_CHNL1MUXING;
+		break;
+	case MCDE_CHANNEL_2:
+		conf = MCDE_CHNL2CONF;
+		sync = MCDE_CHNL2SYNCHMOD;
+		stat = MCDE_CHNL2STAT;
+		bgcol = MCDE_CHNL2BCKGNDCOL;
+		mux = MCDE_CHNL2MUXING;
+		break;
+	case MCDE_CHANNEL_3:
+		conf = MCDE_CHNL3CONF;
+		sync = MCDE_CHNL3SYNCHMOD;
+		stat = MCDE_CHNL3STAT;
+		bgcol = MCDE_CHNL3BCKGNDCOL;
+		mux = MCDE_CHNL3MUXING;
+		return;
+	}
+
+	/* Set up channel 0 sync (based on chnl_update_registers()) */
+	if (mcde->te_sync) {
+		/*
+		 * Turn on hardware TE0 synchronization
+		 */
+		val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE
+			<< MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT;
+		val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0
+			<< MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT;
+	} else {
+		/*
+		 * Set up sync source to software, out sync formatter
+		 * Code mostly from mcde_hw.c chnl_update_registers()
+		 */
+		val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE
+			<< MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT;
+		val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER
+			<< MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT;
+	}
+	writel(val, mcde->regs + sync);
+
+	/* Set up pixels per line and lines per frame */
+	val = (mode->hdisplay - 1) << MCDE_CHNLXCONF_PPL_SHIFT;
+	val |= (mode->vdisplay - 1) << MCDE_CHNLXCONF_LPF_SHIFT;
+	writel(val, mcde->regs + conf);
+
+	/*
+	 * Normalize color conversion:
+	 * black background, OLED conversion disable on channel
+	 */
+	val = MCDE_CHNLXSTAT_CHNLBLBCKGND_EN |
+		MCDE_CHNLXSTAT_CHNLRD;
+	writel(val, mcde->regs + stat);
+	writel(0, mcde->regs + bgcol);
+
+	/* Set up muxing: connect the channel to the desired FIFO */
+	switch (fifo) {
+	case MCDE_FIFO_A:
+		writel(MCDE_CHNLXMUXING_FIFO_ID_FIFO_A,
+		       mcde->regs + mux);
+		break;
+	case MCDE_FIFO_B:
+		writel(MCDE_CHNLXMUXING_FIFO_ID_FIFO_B,
+		       mcde->regs + mux);
+		break;
+	}
+}
+
+static void mcde_configure_fifo(struct mcde *mcde, enum mcde_fifo fifo,
+				enum mcde_dsi_formatter fmt,
+				int fifo_wtrmrk)
+{
+	u32 val;
+	u32 ctrl;
+	u32 cr0, cr1;
+
+	switch (fifo) {
+	case MCDE_FIFO_A:
+		ctrl = MCDE_CTRLA;
+		cr0 = MCDE_CRA0;
+		cr1 = MCDE_CRA1;
+		break;
+	case MCDE_FIFO_B:
+		ctrl = MCDE_CTRLB;
+		cr0 = MCDE_CRB0;
+		cr1 = MCDE_CRB1;
+		break;
+	}
+
+	val = fifo_wtrmrk << MCDE_CTRLX_FIFOWTRMRK_SHIFT;
+	/* We only support DSI formatting for now */
+	val |= MCDE_CTRLX_FORMTYPE_DSI <<
+		MCDE_CTRLX_FORMTYPE_SHIFT;
+
+	/* Select the formatter to use for this FIFO */
+	val |= fmt << MCDE_CTRLX_FORMID_SHIFT;
+	writel(val, mcde->regs + ctrl);
+
+	/* Blend source with Alpha 0xff on FIFO */
+	val = MCDE_CRX0_BLENDEN |
+		0xff << MCDE_CRX0_ALPHABLEND_SHIFT;
+	writel(val, mcde->regs + cr0);
+
+
+	/* Set-up from mcde_fmtr_dsi.c, fmtr_dsi_enable_video() */
+
+	/* Use the MCDE clock for this FIFO */
+	val = MCDE_CRX1_CLKSEL_MCDECLK << MCDE_CRX1_CLKSEL_SHIFT;
+
+	/* TODO: when adding DPI support add OUTBPP etc here */
+	writel(val, mcde->regs + cr1);
+};
+
+static void mcde_configure_dsi_formatter(struct mcde *mcde,
+					 enum mcde_dsi_formatter fmt,
+					 u32 formatter_frame,
+					 int pkt_size)
+{
+	u32 val;
+	u32 conf0;
+	u32 frame;
+	u32 pkt;
+	u32 sync;
+	u32 cmdw;
+	u32 delay0, delay1;
+
+	switch (fmt) {
+	case MCDE_DSI_FORMATTER_0:
+		conf0 = MCDE_DSIVID0CONF0;
+		frame = MCDE_DSIVID0FRAME;
+		pkt = MCDE_DSIVID0PKT;
+		sync = MCDE_DSIVID0SYNC;
+		cmdw = MCDE_DSIVID0CMDW;
+		delay0 = MCDE_DSIVID0DELAY0;
+		delay1 = MCDE_DSIVID0DELAY1;
+		break;
+	case MCDE_DSI_FORMATTER_1:
+		conf0 = MCDE_DSIVID1CONF0;
+		frame = MCDE_DSIVID1FRAME;
+		pkt = MCDE_DSIVID1PKT;
+		sync = MCDE_DSIVID1SYNC;
+		cmdw = MCDE_DSIVID1CMDW;
+		delay0 = MCDE_DSIVID1DELAY0;
+		delay1 = MCDE_DSIVID1DELAY1;
+		break;
+	case MCDE_DSI_FORMATTER_2:
+		conf0 = MCDE_DSIVID2CONF0;
+		frame = MCDE_DSIVID2FRAME;
+		pkt = MCDE_DSIVID2PKT;
+		sync = MCDE_DSIVID2SYNC;
+		cmdw = MCDE_DSIVID2CMDW;
+		delay0 = MCDE_DSIVID2DELAY0;
+		delay1 = MCDE_DSIVID2DELAY1;
+		break;
+	};
+
+	/*
+	 * Enable formatter
+	 * 8 bit commands and DCS commands (notgen = not generic)
+	 */
+	val = MCDE_DSICONF0_CMD8 | MCDE_DSICONF0_DCSVID_NOTGEN;
+	if (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO)
+		val |= MCDE_DSICONF0_VID_MODE_VID;
+	switch (mcde->mdsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+		val |= MCDE_DSICONF0_PACKING_RGB888 <<
+			MCDE_DSICONF0_PACKING_SHIFT;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		val |= MCDE_DSICONF0_PACKING_RGB666 <<
+			MCDE_DSICONF0_PACKING_SHIFT;
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		val |= MCDE_DSICONF0_PACKING_RGB666_PACKED <<
+			MCDE_DSICONF0_PACKING_SHIFT;
+		break;
+	case MIPI_DSI_FMT_RGB565:
+		val |= MCDE_DSICONF0_PACKING_RGB565 <<
+			MCDE_DSICONF0_PACKING_SHIFT;
+		break;
+	default:
+		dev_err(mcde->dev, "unknown DSI format\n");
+		return;
+	}
+	writel(val, mcde->regs + conf0);
+
+	writel(formatter_frame, mcde->regs + frame);
+	writel(pkt_size, mcde->regs + pkt);
+	writel(0, mcde->regs + sync);
+	/* Define the MIPI command: we want to write into display memory */
+	val = MIPI_DCS_WRITE_MEMORY_CONTINUE <<
+		MCDE_DSIVIDXCMDW_CMDW_CONTINUE_SHIFT;
+	val |= MIPI_DCS_WRITE_MEMORY_START <<
+		MCDE_DSIVIDXCMDW_CMDW_START_SHIFT;
+	writel(val, mcde->regs + cmdw);
+
+	/*
+	 * FIXME: the vendor driver has some hack around this value in
+	 * CMD mode with autotrig.
+	 */
+	writel(0, mcde->regs + delay0);
+	writel(0, mcde->regs + delay1);
+}
+
+
+static void mcde_enable_fifo(struct mcde *mcde, enum mcde_fifo fifo)
+{
+	u32 val;
+	u32 cr;
+
+	switch (fifo) {
+	case MCDE_FIFO_A:
+		cr = MCDE_CRA0;
+		break;
+	case MCDE_FIFO_B:
+		cr = MCDE_CRB0;
+		break;
+	default:
+		dev_err(mcde->dev, "cannot enable FIFO %c\n",
+			'A' + fifo);
+		return;
+	}
+
+	spin_lock(&mcde->flow_lock);
+	val = readl(mcde->regs + cr);
+	val |= MCDE_CRX0_FLOEN;
+	writel(val, mcde->regs + cr);
+	mcde->flow_active++;
+	spin_unlock(&mcde->flow_lock);
+}
+
+static void mcde_disable_fifo(struct mcde *mcde, enum mcde_fifo fifo,
+			      bool wait_for_drain)
+{
+	int timeout = 100;
+	u32 val;
+	u32 cr;
+
+	switch (fifo) {
+	case MCDE_FIFO_A:
+		cr = MCDE_CRA0;
+		break;
+	case MCDE_FIFO_B:
+		cr = MCDE_CRB0;
+		break;
+	default:
+		dev_err(mcde->dev, "cannot disable FIFO %c\n",
+			'A' + fifo);
+		return;
+	}
+
+	spin_lock(&mcde->flow_lock);
+	val = readl(mcde->regs + cr);
+	val &= ~MCDE_CRX0_FLOEN;
+	writel(val, mcde->regs + cr);
+	mcde->flow_active = 0;
+	spin_unlock(&mcde->flow_lock);
+
+	if (!wait_for_drain)
+		return;
+
+	/* Check that we really drained and stopped the flow */
+	while (readl(mcde->regs + cr) & MCDE_CRX0_FLOEN) {
+		usleep_range(1000, 1500);
+		if (!--timeout) {
+			dev_err(mcde->dev,
+				"FIFO timeout while clearing FIFO %c\n",
+				'A' + fifo);
+			return;
+		}
+	}
+}
+
+/*
+ * This drains a pipe i.e. a FIFO connected to a certain channel
+ */
+static void mcde_drain_pipe(struct mcde *mcde, enum mcde_fifo fifo,
+			    enum mcde_channel ch)
+{
+	u32 val;
+	u32 ctrl;
+	u32 synsw;
+
+	switch (fifo) {
+	case MCDE_FIFO_A:
+		ctrl = MCDE_CTRLA;
+		break;
+	case MCDE_FIFO_B:
+		ctrl = MCDE_CTRLB;
+		break;
+	}
+
+	switch (ch) {
+	case MCDE_CHANNEL_0:
+		synsw = MCDE_CHNL0SYNCHSW;
+		break;
+	case MCDE_CHANNEL_1:
+		synsw = MCDE_CHNL1SYNCHSW;
+		break;
+	case MCDE_CHANNEL_2:
+		synsw = MCDE_CHNL2SYNCHSW;
+		break;
+	case MCDE_CHANNEL_3:
+		synsw = MCDE_CHNL3SYNCHSW;
+		return;
+	}
+
+	val = readl(mcde->regs + ctrl);
+	if (!(val & MCDE_CTRLX_FIFOEMPTY)) {
+		dev_err(mcde->dev, "Channel A FIFO not empty (handover)\n");
+		/* Attempt to clear the FIFO */
+		mcde_enable_fifo(mcde, fifo);
+		/* Trigger a software sync out on respective channel (0-3) */
+		writel(MCDE_CHNLXSYNCHSW_SW_TRIG, mcde->regs + synsw);
+		/* Disable FIFO A flow again */
+		mcde_disable_fifo(mcde, fifo, true);
+	}
+}
+
+static int mcde_dsi_get_pkt_div(int ppl, int fifo_size)
+{
+	/*
+	 * DSI command mode line packets should be split into an even number of
+	 * packets smaller than or equal to the fifo size.
+	 */
+	int div;
+	const int max_div = DIV_ROUND_UP(MCDE_MAX_WIDTH, fifo_size);
+
+	for (div = 1; div < max_div; div++)
+		if (ppl % div == 0 && ppl / div <= fifo_size)
+			return div;
+	return 1;
+}
+
+static void mcde_display_enable(struct drm_simple_display_pipe *pipe,
+				struct drm_crtc_state *cstate,
+				struct drm_plane_state *plane_state)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_plane *plane = &pipe->plane;
+	struct drm_device *drm = crtc->dev;
+	struct mcde *mcde = drm->dev_private;
+	const struct drm_display_mode *mode = &cstate->mode;
+	struct drm_framebuffer *fb = plane->state->fb;
+	u32 format = fb->format->format;
+	u32 formatter_ppl = mode->hdisplay; /* pixels per line */
+	u32 formatter_lpf = mode->vdisplay; /* lines per frame */
+	int pkt_size, fifo_wtrmrk;
+	int cpp = drm_format_plane_cpp(format, 0);
+	int formatter_cpp;
+	struct drm_format_name_buf tmp;
+	u32 formatter_frame;
+	u32 pkt_div;
+	u32 val;
+
+	dev_info(drm->dev, "enable MCDE, %d x %d format %s\n",
+		 mode->hdisplay, mode->vdisplay,
+		 drm_get_format_name(format, &tmp));
+	if (!mcde->mdsi) {
+		/* TODO: deal with this for non-DSI output */
+		dev_err(drm->dev, "no DSI master attached!\n");
+		return;
+	}
+
+	dev_info(drm->dev, "output in %s mode, format %dbpp\n",
+		 (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ?
+		 "VIDEO" : "CMD",
+		 mipi_dsi_pixel_format_to_bpp(mcde->mdsi->format));
+	formatter_cpp =
+		mipi_dsi_pixel_format_to_bpp(mcde->mdsi->format) / 8;
+	dev_info(drm->dev, "overlay CPP %d bytes, DSI CPP %d bytes\n",
+		 cpp,
+		 formatter_cpp);
+
+	/* Calculations from mcde_fmtr_dsi.c, fmtr_dsi_enable_video() */
+
+	/*
+	 * Set up FIFO A watermark level:
+	 * 128 for LCD 32bpp video mode
+	 * 48  for LCD 32bpp command mode
+	 * 128 for LCD 16bpp video mode
+	 * 64  for LCD 16bpp command mode
+	 * 128 for HDMI 32bpp
+	 * 192 for HDMI 16bpp
+	 */
+	fifo_wtrmrk = mode->hdisplay;
+	if (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
+		fifo_wtrmrk = min(fifo_wtrmrk, 128);
+		pkt_div = 1;
+	} else {
+		fifo_wtrmrk = min(fifo_wtrmrk, 48);
+		/* The FIFO is 640 entries deep on this v3 hardware */
+		pkt_div = mcde_dsi_get_pkt_div(mode->hdisplay, 640);
+	}
+	dev_dbg(drm->dev, "FIFO watermark after flooring: %d bytes\n",
+		 fifo_wtrmrk);
+	dev_dbg(drm->dev, "Packet divisor: %d bytes\n", pkt_div);
+
+
+	/* NOTE: pkt_div is 1 for video mode */
+	pkt_size = (formatter_ppl * formatter_cpp) / pkt_div;
+	/* Commands CMD8 need one extra byte */
+	if (!(mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO))
+		pkt_size++;
+
+	dev_dbg(drm->dev, "DSI packet size: %d * %d bytes per line\n",
+		pkt_size, pkt_div);
+	dev_dbg(drm->dev, "Overlay frame size: %u bytes\n",
+		mode->hdisplay * mode->vdisplay * cpp);
+	mcde->stride = mode->hdisplay * cpp;
+	dev_dbg(drm->dev, "Overlay line stride: %u bytes\n",
+		mcde->stride);
+	/* NOTE: pkt_div is 1 for video mode */
+	formatter_frame = pkt_size * pkt_div * formatter_lpf;
+	dev_dbg(drm->dev, "Formatter frame size: %u bytes\n", formatter_frame);
+
+	/* Drain the FIFO A + channel 0 pipe so we have a clean slate */
+	mcde_drain_pipe(mcde, MCDE_FIFO_A, MCDE_CHANNEL_0);
+
+	/*
+	 * We set up our display pipeline:
+	 * EXTSRC 0 -> OVERLAY 0 -> CHANNEL 0 -> FIFO A -> DSI FORMATTER 0
+	 *
+	 * First configure the external source (memory) on external source 0
+	 * using the desired bitstream/bitmap format
+	 */
+	mcde_configure_extsrc(mcde, MCDE_EXTSRC_0, format);
+
+	/*
+	 * Configure overlay 0 according to format and mode and take input
+	 * from external source 0 and route the output of this overlay to
+	 * channel 0
+	 */
+	mcde_configure_overlay(mcde, MCDE_OVERLAY_0, MCDE_EXTSRC_0,
+			       MCDE_CHANNEL_0, mode, format);
+
+	/*
+	 * Configure pixel-per-line and line-per-frame for channel 0 and then
+	 * route channel 0 to FIFO A
+	 */
+	mcde_configure_channel(mcde, MCDE_CHANNEL_0, MCDE_FIFO_A, mode);
+
+	/* Configure FIFO A to use DSI formatter 0 */
+	mcde_configure_fifo(mcde, MCDE_FIFO_A, MCDE_DSI_FORMATTER_0,
+			    fifo_wtrmrk);
+
+	/* Configure the DSI formatter 0 for the DSI panel output */
+	mcde_configure_dsi_formatter(mcde, MCDE_DSI_FORMATTER_0,
+				     formatter_frame, pkt_size);
+
+	if (mcde->te_sync) {
+		if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+			val = MCDE_VSCRC_VSPOL;
+		else
+			val = 0;
+		writel(val, mcde->regs + MCDE_VSCRC0);
+		/* Enable VSYNC capture on TE0 */
+		val = readl(mcde->regs + MCDE_CRC);
+		val |= MCDE_CRC_SYCEN0;
+		writel(val, mcde->regs + MCDE_CRC);
+
+		drm_crtc_vblank_on(crtc);
+	}
+
+	dev_info(drm->dev, "MCDE display is enabled\n");
+}
+
+static void mcde_display_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_device *drm = crtc->dev;
+	struct mcde *mcde = drm->dev_private;
+
+	if (mcde->te_sync)
+		drm_crtc_vblank_off(crtc);
+
+	/* Disable FIFO A flow */
+	mcde_disable_fifo(mcde, MCDE_FIFO_A, true);
+
+	dev_info(drm->dev, "MCDE display is disabled\n");
+}
+
+static void mcde_display_send_one_frame(struct mcde *mcde)
+{
+	/* Request a TE ACK */
+	if (mcde->te_sync)
+		mcde_dsi_te_request(mcde->mdsi);
+
+	/* Enable FIFO A flow */
+	mcde_enable_fifo(mcde, MCDE_FIFO_A);
+
+	if (mcde->te_sync) {
+		/*
+		 * If oneshot mode is enabled, the flow will be disabled
+		 * when the TE0 IRQ arrives in the interrupt handler. Otherwise
+		 * updates are continuously streamed to the display after this
+		 * point.
+		 */
+		dev_dbg(mcde->dev, "sent TE0 framebuffer update\n");
+		return;
+	}
+
+	/* Trigger a software sync out on channel 0 */
+	writel(MCDE_CHNLXSYNCHSW_SW_TRIG,
+	       mcde->regs + MCDE_CHNL0SYNCHSW);
+
+	/*
+	 * Disable FIFO A flow again: since we are using TE sync we
+	 * need to wait for the FIFO to drain before we continue
+	 * so repeated calls to this function will not cause a mess
+	 * in the hardware by pushing updates will updates are going
+	 * on already.
+	 */
+	mcde_disable_fifo(mcde, MCDE_FIFO_A, true);
+
+	dev_dbg(mcde->dev, "sent SW framebuffer update\n");
+}
+
+static void mcde_set_extsrc(struct mcde *mcde, u32 buffer_address)
+{
+	/* Write bitmap base address to register */
+	writel(buffer_address, mcde->regs + MCDE_EXTSRCXA0);
+	/*
+	 * Base address for next line this is probably only used
+	 * in interlace modes.
+	 */
+	writel(buffer_address + mcde->stride, mcde->regs + MCDE_EXTSRCXA1);
+}
+
+static void mcde_display_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_pstate)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_device *drm = crtc->dev;
+	struct mcde *mcde = drm->dev_private;
+	struct drm_pending_vblank_event *event = crtc->state->event;
+	struct drm_plane *plane = &pipe->plane;
+	struct drm_plane_state *pstate = plane->state;
+	struct drm_framebuffer *fb = pstate->fb;
+
+	/*
+	 * Handle any pending event first, we need to arm the vblank
+	 * interrupt before sending any update to the display so we don't
+	 * miss the interrupt.
+	 */
+	if (event) {
+		crtc->state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		/*
+		 * Hardware must be on before we can arm any vblank event,
+		 * this is not a scanout controller where there is always
+		 * some periodic update going on, it is completely frozen
+		 * until we get an update. If MCDE output isn't yet enabled,
+		 * we just send a vblank dummy event back.
+		 */
+		if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) {
+			dev_dbg(mcde->dev, "arm vblank event\n");
+			drm_crtc_arm_vblank_event(crtc, event);
+		} else {
+			dev_dbg(mcde->dev, "insert fake vblank event\n");
+			drm_crtc_send_vblank_event(crtc, event);
+		}
+
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+
+	/*
+	 * We do not start sending framebuffer updates before the
+	 * display is enabled. Update events will however be dispatched
+	 * from the DRM core before the display is enabled.
+	 */
+	if (fb) {
+		mcde_set_extsrc(mcde, drm_fb_cma_get_gem_addr(fb, pstate, 0));
+		/* Send a single frame using software sync */
+		mcde_display_send_one_frame(mcde);
+		dev_info_once(mcde->dev, "sent first display update\n");
+	} else {
+		/*
+		 * If an update is receieved before the MCDE is enabled
+		 * (before mcde_display_enable() is called) we can't really
+		 * do much with that buffer.
+		 */
+		dev_info(mcde->dev, "ignored a display update\n");
+	}
+}
+
+static int mcde_display_enable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_device *drm = crtc->dev;
+	struct mcde *mcde = drm->dev_private;
+	u32 val;
+
+	/* Enable all VBLANK IRQs */
+	val = MCDE_PP_VCMPA |
+		MCDE_PP_VCMPB |
+		MCDE_PP_VSCC0 |
+		MCDE_PP_VSCC1 |
+		MCDE_PP_VCMPC0 |
+		MCDE_PP_VCMPC1;
+	writel(val, mcde->regs + MCDE_IMSCPP);
+
+	return 0;
+}
+
+static void mcde_display_disable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_device *drm = crtc->dev;
+	struct mcde *mcde = drm->dev_private;
+
+	/* Disable all VBLANK IRQs */
+	writel(0, mcde->regs + MCDE_IMSCPP);
+	/* Clear any pending IRQs */
+	writel(0xFFFFFFFF, mcde->regs + MCDE_RISPP);
+}
+
+static struct drm_simple_display_pipe_funcs mcde_display_funcs = {
+	.check = mcde_display_check,
+	.enable = mcde_display_enable,
+	.disable = mcde_display_disable,
+	.update = mcde_display_update,
+	.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
+};
+
+int mcde_display_init(struct drm_device *drm)
+{
+	struct mcde *mcde = drm->dev_private;
+	int ret;
+	static const u32 formats[] = {
+		DRM_FORMAT_ARGB8888,
+		DRM_FORMAT_ABGR8888,
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_XBGR8888,
+		DRM_FORMAT_RGB888,
+		DRM_FORMAT_BGR888,
+		DRM_FORMAT_ARGB4444,
+		DRM_FORMAT_ABGR4444,
+		DRM_FORMAT_XRGB4444,
+		DRM_FORMAT_XBGR4444,
+		/* These are actually IRGB1555 so intensity bit is lost */
+		DRM_FORMAT_XRGB1555,
+		DRM_FORMAT_XBGR1555,
+		DRM_FORMAT_RGB565,
+		DRM_FORMAT_BGR565,
+		DRM_FORMAT_YUV422,
+	};
+
+	/* Provide vblank only when we have TE enabled */
+	if (mcde->te_sync) {
+		mcde_display_funcs.enable_vblank = mcde_display_enable_vblank;
+		mcde_display_funcs.disable_vblank = mcde_display_disable_vblank;
+	}
+
+	ret = drm_simple_display_pipe_init(drm, &mcde->pipe,
+					   &mcde_display_funcs,
+					   formats, ARRAY_SIZE(formats),
+					   NULL,
+					   mcde->connector);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mcde_display_init);
diff --git a/drivers/gpu/drm/mcde/mcde_display_regs.h b/drivers/gpu/drm/mcde/mcde_display_regs.h
new file mode 100644
index 000000000000..d3ac7ef5ff9a
--- /dev/null
+++ b/drivers/gpu/drm/mcde/mcde_display_regs.h
@@ -0,0 +1,518 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DRM_MCDE_DISPLAY_REGS
+#define __DRM_MCDE_DISPLAY_REGS
+
+/* PP (pixel processor) interrupts */
+#define MCDE_IMSCPP 0x00000104
+#define MCDE_RISPP 0x00000114
+#define MCDE_MISPP 0x00000124
+#define MCDE_SISPP 0x00000134
+
+#define MCDE_PP_VCMPA BIT(0)
+#define MCDE_PP_VCMPB BIT(1)
+#define MCDE_PP_VSCC0 BIT(2)
+#define MCDE_PP_VSCC1 BIT(3)
+#define MCDE_PP_VCMPC0 BIT(4)
+#define MCDE_PP_VCMPC1 BIT(5)
+#define MCDE_PP_ROTFD_A BIT(6)
+#define MCDE_PP_ROTFD_B BIT(7)
+
+/* Overlay interrupts */
+#define MCDE_IMSCOVL 0x00000108
+#define MCDE_RISOVL 0x00000118
+#define MCDE_MISOVL 0x00000128
+#define MCDE_SISOVL 0x00000138
+
+/* Channel interrupts */
+#define MCDE_IMSCCHNL 0x0000010C
+#define MCDE_RISCHNL 0x0000011C
+#define MCDE_MISCHNL 0x0000012C
+#define MCDE_SISCHNL 0x0000013C
+
+/* X = 0..9 */
+#define MCDE_EXTSRCXA0 0x00000200
+#define MCDE_EXTSRCXA0_GROUPOFFSET 0x20
+#define MCDE_EXTSRCXA0_BASEADDRESS0_SHIFT 3
+#define MCDE_EXTSRCXA0_BASEADDRESS0_MASK 0xFFFFFFF8
+
+#define MCDE_EXTSRCXA1 0x00000204
+#define MCDE_EXTSRCXA1_GROUPOFFSET 0x20
+#define MCDE_EXTSRCXA1_BASEADDRESS1_SHIFT 3
+#define MCDE_EXTSRCXA1_BASEADDRESS1_MASK 0xFFFFFFF8
+
+/* External sources 0..9 */
+#define MCDE_EXTSRC0CONF 0x0000020C
+#define MCDE_EXTSRC1CONF 0x0000022C
+#define MCDE_EXTSRC2CONF 0x0000024C
+#define MCDE_EXTSRC3CONF 0x0000026C
+#define MCDE_EXTSRC4CONF 0x0000028C
+#define MCDE_EXTSRC5CONF 0x000002AC
+#define MCDE_EXTSRC6CONF 0x000002CC
+#define MCDE_EXTSRC7CONF 0x000002EC
+#define MCDE_EXTSRC8CONF 0x0000030C
+#define MCDE_EXTSRC9CONF 0x0000032C
+#define MCDE_EXTSRCXCONF_GROUPOFFSET 0x20
+#define MCDE_EXTSRCXCONF_BUF_ID_SHIFT 0
+#define MCDE_EXTSRCXCONF_BUF_ID_MASK 0x00000003
+#define MCDE_EXTSRCXCONF_BUF_NB_SHIFT 2
+#define MCDE_EXTSRCXCONF_BUF_NB_MASK 0x0000000C
+#define MCDE_EXTSRCXCONF_PRI_OVLID_SHIFT 4
+#define MCDE_EXTSRCXCONF_PRI_OVLID_MASK 0x000000F0
+#define MCDE_EXTSRCXCONF_BPP_SHIFT 8
+#define MCDE_EXTSRCXCONF_BPP_MASK 0x00000F00
+#define MCDE_EXTSRCXCONF_BPP_1BPP_PAL 0
+#define MCDE_EXTSRCXCONF_BPP_2BPP_PAL 1
+#define MCDE_EXTSRCXCONF_BPP_4BPP_PAL 2
+#define MCDE_EXTSRCXCONF_BPP_8BPP_PAL 3
+#define MCDE_EXTSRCXCONF_BPP_RGB444 4
+#define MCDE_EXTSRCXCONF_BPP_ARGB4444 5
+#define MCDE_EXTSRCXCONF_BPP_IRGB1555 6
+#define MCDE_EXTSRCXCONF_BPP_RGB565 7
+#define MCDE_EXTSRCXCONF_BPP_RGB888 8
+#define MCDE_EXTSRCXCONF_BPP_XRGB8888 9
+#define MCDE_EXTSRCXCONF_BPP_ARGB8888 10
+#define MCDE_EXTSRCXCONF_BPP_YCBCR422 11
+#define MCDE_EXTSRCXCONF_BGR BIT(12)
+#define MCDE_EXTSRCXCONF_BEBO BIT(13)
+#define MCDE_EXTSRCXCONF_BEPO BIT(14)
+#define MCDE_EXTSRCXCONF_TUNNELING_BUFFER_HEIGHT_SHIFT 16
+#define MCDE_EXTSRCXCONF_TUNNELING_BUFFER_HEIGHT_MASK 0x0FFF0000
+
+/* External sources 0..9 */
+#define MCDE_EXTSRC0CR 0x00000210
+#define MCDE_EXTSRC1CR 0x00000230
+#define MCDE_EXTSRC2CR 0x00000250
+#define MCDE_EXTSRC3CR 0x00000270
+#define MCDE_EXTSRC4CR 0x00000290
+#define MCDE_EXTSRC5CR 0x000002B0
+#define MCDE_EXTSRC6CR 0x000002D0
+#define MCDE_EXTSRC7CR 0x000002F0
+#define MCDE_EXTSRC8CR 0x00000310
+#define MCDE_EXTSRC9CR 0x00000330
+#define MCDE_EXTSRCXCR_SEL_MOD_SHIFT 0
+#define MCDE_EXTSRCXCR_SEL_MOD_MASK 0x00000003
+#define MCDE_EXTSRCXCR_SEL_MOD_EXTERNAL_SEL 0
+#define MCDE_EXTSRCXCR_SEL_MOD_AUTO_TOGGLE 1
+#define MCDE_EXTSRCXCR_SEL_MOD_SOFTWARE_SEL 2
+#define MCDE_EXTSRCXCR_MULTIOVL_CTRL_PRIMARY BIT(2) /* 0 = all */
+#define MCDE_EXTSRCXCR_FS_DIV_DISABLE BIT(3)
+#define MCDE_EXTSRCXCR_FORCE_FS_DIV BIT(4)
+
+/* Only external source 6 has a second address register */
+#define MCDE_EXTSRC6A2 0x000002C8
+
+/* 6 overlays */
+#define MCDE_OVL0CR 0x00000400
+#define MCDE_OVL1CR 0x00000420
+#define MCDE_OVL2CR 0x00000440
+#define MCDE_OVL3CR 0x00000460
+#define MCDE_OVL4CR 0x00000480
+#define MCDE_OVL5CR 0x000004A0
+#define MCDE_OVLXCR_OVLEN BIT(0)
+#define MCDE_OVLXCR_COLCCTRL_DISABLED 0
+#define MCDE_OVLXCR_COLCCTRL_ENABLED_NO_SAT (1 << 1)
+#define MCDE_OVLXCR_COLCCTRL_ENABLED_SAT (2 << 1)
+#define MCDE_OVLXCR_CKEYGEN BIT(3)
+#define MCDE_OVLXCR_ALPHAPMEN BIT(4)
+#define MCDE_OVLXCR_OVLF BIT(5)
+#define MCDE_OVLXCR_OVLR BIT(6)
+#define MCDE_OVLXCR_OVLB BIT(7)
+#define MCDE_OVLXCR_FETCH_ROPC_SHIFT 8
+#define MCDE_OVLXCR_FETCH_ROPC_MASK 0x0000FF00
+#define MCDE_OVLXCR_STBPRIO_SHIFT 16
+#define MCDE_OVLXCR_STBPRIO_MASK 0x000F0000
+#define MCDE_OVLXCR_BURSTSIZE_SHIFT 20
+#define MCDE_OVLXCR_BURSTSIZE_MASK 0x00F00000
+#define MCDE_OVLXCR_BURSTSIZE_1W 0
+#define MCDE_OVLXCR_BURSTSIZE_2W 1
+#define MCDE_OVLXCR_BURSTSIZE_4W 2
+#define MCDE_OVLXCR_BURSTSIZE_8W 3
+#define MCDE_OVLXCR_BURSTSIZE_16W 4
+#define MCDE_OVLXCR_BURSTSIZE_HW_1W 8
+#define MCDE_OVLXCR_BURSTSIZE_HW_2W 9
+#define MCDE_OVLXCR_BURSTSIZE_HW_4W 10
+#define MCDE_OVLXCR_BURSTSIZE_HW_8W 11
+#define MCDE_OVLXCR_BURSTSIZE_HW_16W 12
+#define MCDE_OVLXCR_MAXOUTSTANDING_SHIFT 24
+#define MCDE_OVLXCR_MAXOUTSTANDING_MASK 0x0F000000
+#define MCDE_OVLXCR_MAXOUTSTANDING_1_REQ 0
+#define MCDE_OVLXCR_MAXOUTSTANDING_2_REQ 1
+#define MCDE_OVLXCR_MAXOUTSTANDING_4_REQ 2
+#define MCDE_OVLXCR_MAXOUTSTANDING_8_REQ 3
+#define MCDE_OVLXCR_MAXOUTSTANDING_16_REQ 4
+#define MCDE_OVLXCR_ROTBURSTSIZE_SHIFT 28
+#define MCDE_OVLXCR_ROTBURSTSIZE_MASK 0xF0000000
+#define MCDE_OVLXCR_ROTBURSTSIZE_1W 0
+#define MCDE_OVLXCR_ROTBURSTSIZE_2W 1
+#define MCDE_OVLXCR_ROTBURSTSIZE_4W 2
+#define MCDE_OVLXCR_ROTBURSTSIZE_8W 3
+#define MCDE_OVLXCR_ROTBURSTSIZE_16W 4
+#define MCDE_OVLXCR_ROTBURSTSIZE_HW_1W 8
+#define MCDE_OVLXCR_ROTBURSTSIZE_HW_2W 9
+#define MCDE_OVLXCR_ROTBURSTSIZE_HW_4W 10
+#define MCDE_OVLXCR_ROTBURSTSIZE_HW_8W 11
+#define MCDE_OVLXCR_ROTBURSTSIZE_HW_16W 12
+
+#define MCDE_OVL0CONF 0x00000404
+#define MCDE_OVL1CONF 0x00000424
+#define MCDE_OVL2CONF 0x00000444
+#define MCDE_OVL3CONF 0x00000464
+#define MCDE_OVL4CONF 0x00000484
+#define MCDE_OVL5CONF 0x000004A4
+#define MCDE_OVLXCONF_PPL_SHIFT 0
+#define MCDE_OVLXCONF_PPL_MASK 0x000007FF
+#define MCDE_OVLXCONF_EXTSRC_ID_SHIFT 11
+#define MCDE_OVLXCONF_EXTSRC_ID_MASK 0x00007800
+#define MCDE_OVLXCONF_LPF_SHIFT 16
+#define MCDE_OVLXCONF_LPF_MASK 0x07FF0000
+
+#define MCDE_OVL0CONF2 0x00000408
+#define MCDE_OVL1CONF2 0x00000428
+#define MCDE_OVL2CONF2 0x00000448
+#define MCDE_OVL3CONF2 0x00000468
+#define MCDE_OVL4CONF2 0x00000488
+#define MCDE_OVL5CONF2 0x000004A8
+#define MCDE_OVLXCONF2_BP_PER_PIXEL_ALPHA 0
+#define MCDE_OVLXCONF2_BP_CONSTANT_ALPHA BIT(0)
+#define MCDE_OVLXCONF2_ALPHAVALUE_SHIFT 1
+#define MCDE_OVLXCONF2_ALPHAVALUE_MASK 0x000001FE
+#define MCDE_OVLXCONF2_OPQ BIT(9)
+#define MCDE_OVLXCONF2_PIXOFF_SHIFT 10
+#define MCDE_OVLXCONF2_PIXOFF_MASK 0x0000FC00
+#define MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_SHIFT 16
+#define MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_MASK 0x1FFF0000
+
+#define MCDE_OVL0LJINC 0x0000040C
+#define MCDE_OVL1LJINC 0x0000042C
+#define MCDE_OVL2LJINC 0x0000044C
+#define MCDE_OVL3LJINC 0x0000046C
+#define MCDE_OVL4LJINC 0x0000048C
+#define MCDE_OVL5LJINC 0x000004AC
+
+#define MCDE_OVL0CROP 0x00000410
+#define MCDE_OVL1CROP 0x00000430
+#define MCDE_OVL2CROP 0x00000450
+#define MCDE_OVL3CROP 0x00000470
+#define MCDE_OVL4CROP 0x00000490
+#define MCDE_OVL5CROP 0x000004B0
+#define MCDE_OVLXCROP_TMRGN_SHIFT 0
+#define MCDE_OVLXCROP_TMRGN_MASK 0x003FFFFF
+#define MCDE_OVLXCROP_LMRGN_SHIFT 22
+#define MCDE_OVLXCROP_LMRGN_MASK 0xFFC00000
+
+#define MCDE_OVL0COMP 0x00000414
+#define MCDE_OVL1COMP 0x00000434
+#define MCDE_OVL2COMP 0x00000454
+#define MCDE_OVL3COMP 0x00000474
+#define MCDE_OVL4COMP 0x00000494
+#define MCDE_OVL5COMP 0x000004B4
+#define MCDE_OVLXCOMP_XPOS_SHIFT 0
+#define MCDE_OVLXCOMP_XPOS_MASK 0x000007FF
+#define MCDE_OVLXCOMP_CH_ID_SHIFT 11
+#define MCDE_OVLXCOMP_CH_ID_MASK 0x00007800
+#define MCDE_OVLXCOMP_YPOS_SHIFT 16
+#define MCDE_OVLXCOMP_YPOS_MASK 0x07FF0000
+#define MCDE_OVLXCOMP_Z_SHIFT 27
+#define MCDE_OVLXCOMP_Z_MASK 0x78000000
+
+#define MCDE_CRC 0x00000C00
+#define MCDE_CRC_C1EN BIT(2)
+#define MCDE_CRC_C2EN BIT(3)
+#define MCDE_CRC_SYCEN0 BIT(7)
+#define MCDE_CRC_SYCEN1 BIT(8)
+#define MCDE_CRC_SIZE1 BIT(9)
+#define MCDE_CRC_SIZE2 BIT(10)
+#define MCDE_CRC_YUVCONVC1EN BIT(15)
+#define MCDE_CRC_CS1EN BIT(16)
+#define MCDE_CRC_CS2EN BIT(17)
+#define MCDE_CRC_CS1POL BIT(19)
+#define MCDE_CRC_CS2POL BIT(20)
+#define MCDE_CRC_CD1POL BIT(21)
+#define MCDE_CRC_CD2POL BIT(22)
+#define MCDE_CRC_WR1POL BIT(23)
+#define MCDE_CRC_WR2POL BIT(24)
+#define MCDE_CRC_RD1POL BIT(25)
+#define MCDE_CRC_RD2POL BIT(26)
+#define MCDE_CRC_SYNCCTRL_SHIFT 29
+#define MCDE_CRC_SYNCCTRL_MASK 0x60000000
+#define MCDE_CRC_SYNCCTRL_NO_SYNC 0
+#define MCDE_CRC_SYNCCTRL_DBI0 1
+#define MCDE_CRC_SYNCCTRL_DBI1 2
+#define MCDE_CRC_SYNCCTRL_PING_PONG 3
+#define MCDE_CRC_CLAMPC1EN BIT(31)
+
+#define MCDE_VSCRC0 0x00000C5C
+#define MCDE_VSCRC1 0x00000C60
+#define MCDE_VSCRC_VSPMIN_MASK 0x00000FFF
+#define MCDE_VSCRC_VSPMAX_SHIFT 12
+#define MCDE_VSCRC_VSPMAX_MASK 0x00FFF000
+#define MCDE_VSCRC_VSPDIV_SHIFT 24
+#define MCDE_VSCRC_VSPDIV_MASK 0x07000000
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_1 0
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_2 1
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_4 2
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_8 3
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_16 4
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_32 5
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_64 6
+#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_128 7
+#define MCDE_VSCRC_VSPOL BIT(27) /* 0 active high, 1 active low */
+#define MCDE_VSCRC_VSSEL BIT(28) /* 0 VSYNC0, 1 VSYNC1 */
+#define MCDE_VSCRC_VSDBL BIT(29)
+
+/* Channel config 0..3 */
+#define MCDE_CHNL0CONF 0x00000600
+#define MCDE_CHNL1CONF 0x00000620
+#define MCDE_CHNL2CONF 0x00000640
+#define MCDE_CHNL3CONF 0x00000660
+#define MCDE_CHNLXCONF_PPL_SHIFT 0
+#define MCDE_CHNLXCONF_PPL_MASK 0x000007FF
+#define MCDE_CHNLXCONF_LPF_SHIFT 16
+#define MCDE_CHNLXCONF_LPF_MASK 0x07FF0000
+#define MCDE_MAX_WIDTH 2048
+
+/* Channel status 0..3 */
+#define MCDE_CHNL0STAT 0x00000604
+#define MCDE_CHNL1STAT 0x00000624
+#define MCDE_CHNL2STAT 0x00000644
+#define MCDE_CHNL3STAT 0x00000664
+#define MCDE_CHNLXSTAT_CHNLRD BIT(0)
+#define MCDE_CHNLXSTAT_CHNLA BIT(1)
+#define MCDE_CHNLXSTAT_CHNLBLBCKGND_EN BIT(16)
+#define MCDE_CHNLXSTAT_PPLX2_V422 BIT(17)
+#define MCDE_CHNLXSTAT_LPFX2_V422 BIT(18)
+
+/* Sync settings for channel 0..3 */
+#define MCDE_CHNL0SYNCHMOD 0x00000608
+#define MCDE_CHNL1SYNCHMOD 0x00000628
+#define MCDE_CHNL2SYNCHMOD 0x00000648
+#define MCDE_CHNL3SYNCHMOD 0x00000668
+
+#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT 0
+#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_MASK 0x00000003
+#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE 0
+#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_NO_SYNCH 1
+#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE 2
+#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT 2
+#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_MASK 0x0000001C
+#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER 0
+#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 1
+#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE1 2
+
+/* Software sync triggers for channel 0..3 */
+#define MCDE_CHNL0SYNCHSW 0x0000060C
+#define MCDE_CHNL1SYNCHSW 0x0000062C
+#define MCDE_CHNL2SYNCHSW 0x0000064C
+#define MCDE_CHNL3SYNCHSW 0x0000066C
+#define MCDE_CHNLXSYNCHSW_SW_TRIG BIT(0)
+
+#define MCDE_CHNL0BCKGNDCOL 0x00000610
+#define MCDE_CHNL1BCKGNDCOL 0x00000630
+#define MCDE_CHNL2BCKGNDCOL 0x00000650
+#define MCDE_CHNL3BCKGNDCOL 0x00000670
+#define MCDE_CHNLXBCKGNDCOL_B_SHIFT 0
+#define MCDE_CHNLXBCKGNDCOL_B_MASK 0x000000FF
+#define MCDE_CHNLXBCKGNDCOL_G_SHIFT 8
+#define MCDE_CHNLXBCKGNDCOL_G_MASK 0x0000FF00
+#define MCDE_CHNLXBCKGNDCOL_R_SHIFT 16
+#define MCDE_CHNLXBCKGNDCOL_R_MASK 0x00FF0000
+
+#define MCDE_CHNL0MUXING 0x00000614
+#define MCDE_CHNL1MUXING 0x00000634
+#define MCDE_CHNL2MUXING 0x00000654
+#define MCDE_CHNL3MUXING 0x00000674
+#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_A 0
+#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_B 1
+#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_C0 2
+#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_C1 3
+
+/* Pixel processing control registers for channel A B,  */
+#define MCDE_CRA0 0x00000800
+#define MCDE_CRB0 0x00000A00
+#define MCDE_CRX0_FLOEN BIT(0)
+#define MCDE_CRX0_POWEREN BIT(1)
+#define MCDE_CRX0_BLENDEN BIT(2)
+#define MCDE_CRX0_AFLICKEN BIT(3)
+#define MCDE_CRX0_PALEN BIT(4)
+#define MCDE_CRX0_DITHEN BIT(5)
+#define MCDE_CRX0_GAMEN BIT(6)
+#define MCDE_CRX0_KEYCTRL_SHIFT 7
+#define MCDE_CRX0_KEYCTRL_MASK 0x00000380
+#define MCDE_CRX0_KEYCTRL_OFF 0
+#define MCDE_CRX0_KEYCTRL_ALPHA_RGB 1
+#define MCDE_CRX0_KEYCTRL_RGB 2
+#define MCDE_CRX0_KEYCTRL_FALPHA_FRGB 4
+#define MCDE_CRX0_KEYCTRL_FRGB 5
+#define MCDE_CRX0_BLENDCTRL BIT(10)
+#define MCDE_CRX0_FLICKMODE_SHIFT 11
+#define MCDE_CRX0_FLICKMODE_MASK 0x00001800
+#define MCDE_CRX0_FLICKMODE_FORCE_FILTER_0 0
+#define MCDE_CRX0_FLICKMODE_ADAPTIVE 1
+#define MCDE_CRX0_FLICKMODE_TEST_MODE 2
+#define MCDE_CRX0_FLOCKFORMAT_RGB BIT(13) /* 0 = YCVCR */
+#define MCDE_CRX0_PALMODE_GAMMA BIT(14) /* 0 = palette */
+#define MCDE_CRX0_OLEDEN BIT(15)
+#define MCDE_CRX0_ALPHABLEND_SHIFT 16
+#define MCDE_CRX0_ALPHABLEND_MASK 0x00FF0000
+#define MCDE_CRX0_ROTEN BIT(24)
+
+#define MCDE_CRA1 0x00000804
+#define MCDE_CRB1 0x00000A04
+#define MCDE_CRX1_PCD_SHIFT 0
+#define MCDE_CRX1_PCD_MASK 0x000003FF
+#define MCDE_CRX1_CLKSEL_SHIFT 10
+#define MCDE_CRX1_CLKSEL_MASK 0x00001C00
+#define MCDE_CRX1_CLKSEL_CLKPLL72 0
+#define MCDE_CRX1_CLKSEL_CLKPLL27 2
+#define MCDE_CRX1_CLKSEL_TV1CLK 3
+#define MCDE_CRX1_CLKSEL_TV2CLK 4
+#define MCDE_CRX1_CLKSEL_MCDECLK 5
+#define MCDE_CRX1_CDWIN_SHIFT 13
+#define MCDE_CRX1_CDWIN_MASK 0x0001E000
+#define MCDE_CRX1_CDWIN_8BPP_C1 0
+#define MCDE_CRX1_CDWIN_12BPP_C1 1
+#define MCDE_CRX1_CDWIN_12BPP_C2 2
+#define MCDE_CRX1_CDWIN_16BPP_C1 3
+#define MCDE_CRX1_CDWIN_16BPP_C2 4
+#define MCDE_CRX1_CDWIN_16BPP_C3 5
+#define MCDE_CRX1_CDWIN_18BPP_C1 6
+#define MCDE_CRX1_CDWIN_18BPP_C2 7
+#define MCDE_CRX1_CDWIN_24BPP 8
+#define MCDE_CRX1_OUTBPP_SHIFT 25
+#define MCDE_CRX1_OUTBPP_MASK 0x1E000000
+#define MCDE_CRX1_OUTBPP_MONO1 0
+#define MCDE_CRX1_OUTBPP_MONO2 1
+#define MCDE_CRX1_OUTBPP_MONO4 2
+#define MCDE_CRX1_OUTBPP_MONO8 3
+#define MCDE_CRX1_OUTBPP_8BPP 4
+#define MCDE_CRX1_OUTBPP_12BPP 5
+#define MCDE_CRX1_OUTBPP_15BPP 6
+#define MCDE_CRX1_OUTBPP_16BPP 7
+#define MCDE_CRX1_OUTBPP_18BPP 8
+#define MCDE_CRX1_OUTBPP_24BPP 9
+#define MCDE_CRX1_BCD BIT(29)
+#define MCDE_CRA1_CLKTYPE_TVXCLKSEL1 BIT(30) /* 0 = TVXCLKSEL1 */
+
+#define MCDE_COLKEYA 0x00000808
+#define MCDE_COLKEYB 0x00000A08
+
+#define MCDE_FCOLKEYA 0x0000080C
+#define MCDE_FCOLKEYB 0x00000A0C
+
+#define MCDE_RGBCONV1A 0x00000810
+#define MCDE_RGBCONV1B 0x00000A10
+
+#define MCDE_RGBCONV2A 0x00000814
+#define MCDE_RGBCONV2B 0x00000A14
+
+#define MCDE_RGBCONV3A 0x00000818
+#define MCDE_RGBCONV3B 0x00000A18
+
+#define MCDE_RGBCONV4A 0x0000081C
+#define MCDE_RGBCONV4B 0x00000A1C
+
+#define MCDE_RGBCONV5A 0x00000820
+#define MCDE_RGBCONV5B 0x00000A20
+
+#define MCDE_RGBCONV6A 0x00000824
+#define MCDE_RGBCONV6B 0x00000A24
+
+/* Rotation */
+#define MCDE_ROTACONF 0x0000087C
+#define MCDE_ROTBCONF 0x00000A7C
+
+#define MCDE_SYNCHCONFA 0x00000880
+#define MCDE_SYNCHCONFB 0x00000A80
+
+/* Channel A+B control registers */
+#define MCDE_CTRLA 0x00000884
+#define MCDE_CTRLB 0x00000A84
+#define MCDE_CTRLX_FIFOWTRMRK_SHIFT 0
+#define MCDE_CTRLX_FIFOWTRMRK_MASK 0x000003FF
+#define MCDE_CTRLX_FIFOEMPTY BIT(12)
+#define MCDE_CTRLX_FIFOFULL BIT(13)
+#define MCDE_CTRLX_FORMID_SHIFT 16
+#define MCDE_CTRLX_FORMID_MASK 0x00070000
+#define MCDE_CTRLX_FORMID_DSI0VID 0
+#define MCDE_CTRLX_FORMID_DSI0CMD 1
+#define MCDE_CTRLX_FORMID_DSI1VID 2
+#define MCDE_CTRLX_FORMID_DSI1CMD 3
+#define MCDE_CTRLX_FORMID_DSI2VID 4
+#define MCDE_CTRLX_FORMID_DSI2CMD 5
+#define MCDE_CTRLX_FORMID_DPIA 0
+#define MCDE_CTRLX_FORMID_DPIB 1
+#define MCDE_CTRLX_FORMTYPE_SHIFT 20
+#define MCDE_CTRLX_FORMTYPE_MASK 0x00700000
+#define MCDE_CTRLX_FORMTYPE_DPITV 0
+#define MCDE_CTRLX_FORMTYPE_DBI 1
+#define MCDE_CTRLX_FORMTYPE_DSI 2
+
+#define MCDE_DSIVID0CONF0 0x00000E00
+#define MCDE_DSICMD0CONF0 0x00000E20
+#define MCDE_DSIVID1CONF0 0x00000E40
+#define MCDE_DSICMD1CONF0 0x00000E60
+#define MCDE_DSIVID2CONF0 0x00000E80
+#define MCDE_DSICMD2CONF0 0x00000EA0
+#define MCDE_DSICONF0_BLANKING_SHIFT 0
+#define MCDE_DSICONF0_BLANKING_MASK 0x000000FF
+#define MCDE_DSICONF0_VID_MODE_CMD 0
+#define MCDE_DSICONF0_VID_MODE_VID BIT(12)
+#define MCDE_DSICONF0_CMD8 BIT(13)
+#define MCDE_DSICONF0_BIT_SWAP BIT(16)
+#define MCDE_DSICONF0_BYTE_SWAP BIT(17)
+#define MCDE_DSICONF0_DCSVID_NOTGEN BIT(18)
+#define MCDE_DSICONF0_PACKING_SHIFT 20
+#define MCDE_DSICONF0_PACKING_MASK 0x00700000
+#define MCDE_DSICONF0_PACKING_RGB565 0
+#define MCDE_DSICONF0_PACKING_RGB666 1
+#define MCDE_DSICONF0_PACKING_RGB666_PACKED 2
+#define MCDE_DSICONF0_PACKING_RGB888 3
+#define MCDE_DSICONF0_PACKING_HDTV 4
+
+#define MCDE_DSIVID0FRAME 0x00000E04
+#define MCDE_DSICMD0FRAME 0x00000E24
+#define MCDE_DSIVID1FRAME 0x00000E44
+#define MCDE_DSICMD1FRAME 0x00000E64
+#define MCDE_DSIVID2FRAME 0x00000E84
+#define MCDE_DSICMD2FRAME 0x00000EA4
+
+#define MCDE_DSIVID0PKT 0x00000E08
+#define MCDE_DSICMD0PKT 0x00000E28
+#define MCDE_DSIVID1PKT 0x00000E48
+#define MCDE_DSICMD1PKT 0x00000E68
+#define MCDE_DSIVID2PKT 0x00000E88
+#define MCDE_DSICMD2PKT 0x00000EA8
+
+#define MCDE_DSIVID0SYNC 0x00000E0C
+#define MCDE_DSICMD0SYNC 0x00000E2C
+#define MCDE_DSIVID1SYNC 0x00000E4C
+#define MCDE_DSICMD1SYNC 0x00000E6C
+#define MCDE_DSIVID2SYNC 0x00000E8C
+#define MCDE_DSICMD2SYNC 0x00000EAC
+
+#define MCDE_DSIVID0CMDW 0x00000E10
+#define MCDE_DSICMD0CMDW 0x00000E30
+#define MCDE_DSIVID1CMDW 0x00000E50
+#define MCDE_DSICMD1CMDW 0x00000E70
+#define MCDE_DSIVID2CMDW 0x00000E90
+#define MCDE_DSICMD2CMDW 0x00000EB0
+#define MCDE_DSIVIDXCMDW_CMDW_CONTINUE_SHIFT 0
+#define MCDE_DSIVIDXCMDW_CMDW_CONTINUE_MASK 0x0000FFFF
+#define MCDE_DSIVIDXCMDW_CMDW_START_SHIFT 16
+#define MCDE_DSIVIDXCMDW_CMDW_START_MASK 0xFFFF0000
+
+#define MCDE_DSIVID0DELAY0 0x00000E14
+#define MCDE_DSICMD0DELAY0 0x00000E34
+#define MCDE_DSIVID1DELAY0 0x00000E54
+#define MCDE_DSICMD1DELAY0 0x00000E74
+#define MCDE_DSIVID2DELAY0 0x00000E94
+#define MCDE_DSICMD2DELAY0 0x00000EB4
+
+#define MCDE_DSIVID0DELAY1 0x00000E18
+#define MCDE_DSICMD0DELAY1 0x00000E38
+#define MCDE_DSIVID1DELAY1 0x00000E58
+#define MCDE_DSICMD1DELAY1 0x00000E78
+#define MCDE_DSIVID2DELAY1 0x00000E98
+#define MCDE_DSICMD2DELAY1 0x00000EB8
+
+#endif /* __DRM_MCDE_DISPLAY_REGS */
diff --git a/drivers/gpu/drm/mcde/mcde_drm.h b/drivers/gpu/drm/mcde/mcde_drm.h
new file mode 100644
index 000000000000..dab4db021231
--- /dev/null
+++ b/drivers/gpu/drm/mcde/mcde_drm.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 Linus Walleij <linus.walleij at linaro.org>
+ * Parts of this file were based on the MCDE driver by Marcus Lorentzon
+ * (C) ST-Ericsson SA 2013
+ */
+#include <drm/drm_simple_kms_helper.h>
+
+#ifndef _MCDE_DRM_H_
+#define _MCDE_DRM_H_
+
+struct mcde {
+	struct drm_device drm;
+	struct device *dev;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+	struct drm_connector *connector;
+	struct drm_simple_display_pipe pipe;
+	struct mipi_dsi_device *mdsi;
+	s16 stride;
+	bool te_sync;
+	bool oneshot_mode;
+	unsigned int flow_active;
+	spinlock_t flow_lock; /* Locks the channel flow control */
+
+	void __iomem *regs;
+
+	struct clk *mcde_clk;
+	struct clk *lcd_clk;
+	struct clk *hdmi_clk;
+
+	struct regulator *epod;
+	struct regulator *vana;
+};
+
+bool mcde_dsi_irq(struct mipi_dsi_device *mdsi);
+void mcde_dsi_te_request(struct mipi_dsi_device *mdsi);
+extern struct platform_driver mcde_dsi_driver;
+
+void mcde_display_irq(struct mcde *mcde);
+void mcde_display_disable_irqs(struct mcde *mcde);
+int mcde_display_init(struct drm_device *drm);
+
+#endif /* _MCDE_DRM_H_ */
diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c
new file mode 100644
index 000000000000..baf63fb6850a
--- /dev/null
+++ b/drivers/gpu/drm/mcde/mcde_drv.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Linus Walleij <linus.walleij at linaro.org>
+ * Parts of this file were based on the MCDE driver by Marcus Lorentzon
+ * (C) ST-Ericsson SA 2013
+ */
+
+/**
+ * DOC: ST-Ericsson MCDE Driver
+ *
+ * The MCDE (short for multi-channel display engine) is a graphics
+ * controller found in the Ux500 chipsets, such as NovaThor U8500.
+ * It was initially conceptualized by ST Microelectronics for the
+ * successor of the Nomadik line, STn8500 but productified in the
+ * ST-Ericsson U8500 where is was used for mass-market deployments
+ * in Android phones from Samsung and Sony Ericsson.
+ *
+ * It can do 1080p30 on SDTV CCIR656, DPI-2, DBI-2 or DSI for
+ * panels with or without frame buffering and can convert most
+ * input formats including most variants of RGB and YUV.
+ *
+ * The hardware has four display pipes, and the layout is a little
+ * bit like this:
+ *
+ * Memory     -> Overlay -> Channel -> FIFO -> 5 formatters -> DSI/DPI
+ * External      0..5       0..3       A,B,    3 x DSI         bridge
+ * source 0..9                         C0,C1   2 x DPI
+ *
+ * FIFOs A and B are for LCD and HDMI while FIFO CO/C1 are for
+ * panels with embedded buffer.
+ * 3 of the formatters are for DSI.
+ * 2 of the formatters are for DPI.
+ *
+ * Behind the formatters are the DSI or DPI ports that route to
+ * the external pins of the chip. As there are 3 DSI ports and one
+ * DPI port, it is possible to configure up to 4 display pipelines
+ * (effectively using channels 0..3) for concurrent use.
+ *
+ * In the current DRM/KMS setup, we use one external source, one overlay,
+ * one FIFO and one formatter which we connect to the simple CMA framebuffer
+ * helpers. We then provide a bridge to the DSI port, and on the DSI port
+ * bridge we connect hang a panel bridge or other bridge. This may be subject
+ * to change as we exploit more of the hardware capabilities.
+ *
+ * TODO:
+ * - Enabled damaged rectangles using drm_plane_enable_fb_damage_clips()
+ *   so we can selectively just transmit the damaged area to a
+ *   command-only display.
+ * - Enable mixing of more planes, possibly at the cost of moving away
+ *   from using the simple framebuffer pipeline.
+ * - Enable output to bridges such as the AV8100 HDMI encoder from
+ *   the DSI bridge.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/dma-buf.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_vblank.h>
+
+#include "mcde_drm.h"
+
+#define DRIVER_DESC	"DRM module for MCDE"
+
+#define MCDE_CR 0x00000000
+#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_SHIFT 0
+#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_MASK 0x0000003F
+#define MCDE_CR_IFIFOCTRLEN BIT(15)
+#define MCDE_CR_UFRECOVERY_MODE_V422 BIT(16)
+#define MCDE_CR_WRAP_MODE_V422_SHIFT BIT(17)
+#define MCDE_CR_AUTOCLKG_EN BIT(30)
+#define MCDE_CR_MCDEEN BIT(31)
+
+#define MCDE_CONF0 0x00000004
+#define MCDE_CONF0_SYNCMUX0 BIT(0)
+#define MCDE_CONF0_SYNCMUX1 BIT(1)
+#define MCDE_CONF0_SYNCMUX2 BIT(2)
+#define MCDE_CONF0_SYNCMUX3 BIT(3)
+#define MCDE_CONF0_SYNCMUX4 BIT(4)
+#define MCDE_CONF0_SYNCMUX5 BIT(5)
+#define MCDE_CONF0_SYNCMUX6 BIT(6)
+#define MCDE_CONF0_SYNCMUX7 BIT(7)
+#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT 12
+#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_MASK 0x00007000
+#define MCDE_CONF0_OUTMUX0_SHIFT 16
+#define MCDE_CONF0_OUTMUX0_MASK 0x00070000
+#define MCDE_CONF0_OUTMUX1_SHIFT 19
+#define MCDE_CONF0_OUTMUX1_MASK 0x00380000
+#define MCDE_CONF0_OUTMUX2_SHIFT 22
+#define MCDE_CONF0_OUTMUX2_MASK 0x01C00000
+#define MCDE_CONF0_OUTMUX3_SHIFT 25
+#define MCDE_CONF0_OUTMUX3_MASK 0x0E000000
+#define MCDE_CONF0_OUTMUX4_SHIFT 28
+#define MCDE_CONF0_OUTMUX4_MASK 0x70000000
+
+#define MCDE_SSP 0x00000008
+#define MCDE_AIS 0x00000100
+#define MCDE_IMSCERR 0x00000110
+#define MCDE_RISERR 0x00000120
+#define MCDE_MISERR 0x00000130
+#define MCDE_SISERR 0x00000140
+
+#define MCDE_PID 0x000001FC
+#define MCDE_PID_METALFIX_VERSION_SHIFT 0
+#define MCDE_PID_METALFIX_VERSION_MASK 0x000000FF
+#define MCDE_PID_DEVELOPMENT_VERSION_SHIFT 8
+#define MCDE_PID_DEVELOPMENT_VERSION_MASK 0x0000FF00
+#define MCDE_PID_MINOR_VERSION_SHIFT 16
+#define MCDE_PID_MINOR_VERSION_MASK 0x00FF0000
+#define MCDE_PID_MAJOR_VERSION_SHIFT 24
+#define MCDE_PID_MAJOR_VERSION_MASK 0xFF000000
+
+static const struct drm_mode_config_funcs mcde_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static const struct drm_mode_config_helper_funcs mcde_mode_config_helpers = {
+	/*
+	 * Using this function is necessary to commit atomic updates
+	 * that need the CRTC to be enabled before a commit, as is
+	 * the case with e.g. DSI displays.
+	 */
+	.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+static irqreturn_t mcde_irq(int irq, void *data)
+{
+	struct mcde *mcde = data;
+	u32 val;
+
+	val = readl(mcde->regs + MCDE_MISERR);
+
+	mcde_display_irq(mcde);
+
+	if (val)
+		dev_info(mcde->dev, "some error IRQ\n");
+	writel(val, mcde->regs + MCDE_RISERR);
+
+	return IRQ_HANDLED;
+}
+
+static int mcde_modeset_init(struct drm_device *drm)
+{
+	struct drm_mode_config *mode_config;
+	struct mcde *mcde = drm->dev_private;
+	int ret;
+
+	if (!mcde->bridge) {
+		dev_err(drm->dev, "no display output bridge yet\n");
+		return -EPROBE_DEFER;
+	}
+
+	mode_config = &drm->mode_config;
+	mode_config->funcs = &mcde_mode_config_funcs;
+	mode_config->helper_private = &mcde_mode_config_helpers;
+	/* This hardware can do 1080p */
+	mode_config->min_width = 1;
+	mode_config->max_width = 1920;
+	mode_config->min_height = 1;
+	mode_config->max_height = 1080;
+
+	/*
+	 * Currently we only support vblank handling on the DSI bridge, using
+	 * TE synchronization. If TE sync is not set up, it is still possible
+	 * to push out a single update on demand, but this is hard for DRM to
+	 * exploit.
+	 */
+	if (mcde->te_sync) {
+		ret = drm_vblank_init(drm, 1);
+		if (ret) {
+			dev_err(drm->dev, "failed to init vblank\n");
+			goto out_config;
+		}
+	}
+
+	ret = mcde_display_init(drm);
+	if (ret) {
+		dev_err(drm->dev, "failed to init display\n");
+		goto out_config;
+	}
+
+	/*
+	 * Attach the DSI bridge
+	 *
+	 * TODO: when adding support for the DPI bridge or several DSI bridges,
+	 * we selectively connect the bridge(s) here instead of this simple
+	 * attachment.
+	 */
+	ret = drm_simple_display_pipe_attach_bridge(&mcde->pipe,
+						    mcde->bridge);
+	if (ret) {
+		dev_err(drm->dev, "failed to attach display output bridge\n");
+		goto out_config;
+	}
+
+	drm_mode_config_reset(drm);
+	drm_kms_helper_poll_init(drm);
+	drm_fbdev_generic_setup(drm, 32);
+
+	return 0;
+
+out_config:
+	drm_mode_config_cleanup(drm);
+	return ret;
+}
+
+static void mcde_release(struct drm_device *drm)
+{
+	struct mcde *mcde = drm->dev_private;
+
+	drm_mode_config_cleanup(drm);
+	drm_dev_fini(drm);
+	kfree(mcde);
+}
+
+DEFINE_DRM_GEM_CMA_FOPS(drm_fops);
+
+static struct drm_driver mcde_drm_driver = {
+	.driver_features =
+		DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC,
+	.release = mcde_release,
+	.lastclose = drm_fb_helper_lastclose,
+	.ioctls = NULL,
+	.fops = &drm_fops,
+	.name = "mcde",
+	.desc = DRIVER_DESC,
+	.date = "20180529",
+	.major = 1,
+	.minor = 0,
+	.patchlevel = 0,
+	.dumb_create = drm_gem_cma_dumb_create,
+	.gem_free_object_unlocked = 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,
+};
+
+static int mcde_drm_bind(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	int ret;
+
+	drm_mode_config_init(drm);
+
+	ret = component_bind_all(drm->dev, drm);
+	if (ret) {
+		dev_err(dev, "can't bind component devices\n");
+		return ret;
+	}
+
+	ret = mcde_modeset_init(drm);
+	if (ret)
+		goto unbind;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto unbind;
+
+	return 0;
+
+unbind:
+	component_unbind_all(drm->dev, drm);
+	return ret;
+}
+
+static void mcde_drm_unbind(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	drm_dev_unregister(drm);
+	drm_atomic_helper_shutdown(drm);
+	component_unbind_all(drm->dev, drm);
+}
+
+static const struct component_master_ops mcde_drm_comp_ops = {
+	.bind = mcde_drm_bind,
+	.unbind = mcde_drm_unbind,
+};
+
+static struct platform_driver *const mcde_component_drivers[] = {
+	&mcde_dsi_driver,
+};
+
+static int mcde_compare_dev(struct device *dev, void *data)
+{
+	return dev == data;
+}
+
+static int mcde_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct drm_device *drm;
+	struct mcde *mcde;
+	struct component_match *match;
+	struct resource *res;
+	u32 pid;
+	u32 val;
+	int irq;
+	int ret;
+	int i;
+
+	mcde = kzalloc(sizeof(*mcde), GFP_KERNEL);
+	if (!mcde)
+		return -ENOMEM;
+	mcde->dev = dev;
+
+	ret = drm_dev_init(&mcde->drm, &mcde_drm_driver, dev);
+	if (ret) {
+		kfree(mcde);
+		return ret;
+	}
+	drm = &mcde->drm;
+	drm->dev_private = mcde;
+	platform_set_drvdata(pdev, drm);
+
+	/* Enable use of the TE signal and interrupt */
+	mcde->te_sync = true;
+	/* Enable continuous updates: this is what Linux' framebuffer expects */
+	mcde->oneshot_mode = false;
+	drm->dev_private = mcde;
+
+	/* First obtain and turn on the main power */
+	mcde->epod = devm_regulator_get(dev, "epod");
+	if (IS_ERR(mcde->epod)) {
+		ret = PTR_ERR(mcde->epod);
+		dev_err(dev, "can't get EPOD regulator\n");
+		goto dev_unref;
+	}
+	ret = regulator_enable(mcde->epod);
+	if (ret) {
+		dev_err(dev, "can't enable EPOD regulator\n");
+		goto dev_unref;
+	}
+	mcde->vana = devm_regulator_get(dev, "vana");
+	if (IS_ERR(mcde->vana)) {
+		ret = PTR_ERR(mcde->vana);
+		dev_err(dev, "can't get VANA regulator\n");
+		goto regulator_epod_off;
+	}
+	ret = regulator_enable(mcde->vana);
+	if (ret) {
+		dev_err(dev, "can't enable VANA regulator\n");
+		goto regulator_epod_off;
+	}
+	/*
+	 * The vendor code uses ESRAM (onchip RAM) and need to activate
+	 * the v-esram34 regulator, but we don't use that yet
+	 */
+
+	/* Clock the silicon so we can access the registers */
+	mcde->mcde_clk = devm_clk_get(dev, "mcde");
+	if (IS_ERR(mcde->mcde_clk)) {
+		dev_err(dev, "unable to get MCDE main clock\n");
+		ret = PTR_ERR(mcde->mcde_clk);
+		goto regulator_off;
+	}
+	ret = clk_prepare_enable(mcde->mcde_clk);
+	if (ret) {
+		dev_err(dev, "failed to enable MCDE main clock\n");
+		goto regulator_off;
+	}
+	dev_info(dev, "MCDE clk rate %lu Hz\n", clk_get_rate(mcde->mcde_clk));
+
+	mcde->lcd_clk = devm_clk_get(dev, "lcd");
+	if (IS_ERR(mcde->lcd_clk)) {
+		dev_err(dev, "unable to get LCD clock\n");
+		ret = PTR_ERR(mcde->lcd_clk);
+		goto clk_disable;
+	}
+	mcde->hdmi_clk = devm_clk_get(dev, "hdmi");
+	if (IS_ERR(mcde->hdmi_clk)) {
+		dev_err(dev, "unable to get HDMI clock\n");
+		ret = PTR_ERR(mcde->hdmi_clk);
+		goto clk_disable;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mcde->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(mcde->regs)) {
+		dev_err(dev, "no MCDE regs\n");
+		ret = -EINVAL;
+		goto clk_disable;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (!irq) {
+		ret = -EINVAL;
+		goto clk_disable;
+	}
+
+	ret = devm_request_irq(dev, irq, mcde_irq, 0, "mcde", mcde);
+	if (ret) {
+		dev_err(dev, "failed to request irq %d\n", ret);
+		goto clk_disable;
+	}
+
+	/*
+	 * Check hardware revision, we only support U8500v2 version
+	 * as this was the only version used for mass market deployment,
+	 * but surely you can add more versions if you have them and
+	 * need them.
+	 */
+	pid = readl(mcde->regs + MCDE_PID);
+	dev_info(dev, "found MCDE HW revision %d.%d (dev %d, metal fix %d)\n",
+		 (pid & MCDE_PID_MAJOR_VERSION_MASK)
+		 >> MCDE_PID_MAJOR_VERSION_SHIFT,
+		 (pid & MCDE_PID_MINOR_VERSION_MASK)
+		 >> MCDE_PID_MINOR_VERSION_SHIFT,
+		 (pid & MCDE_PID_DEVELOPMENT_VERSION_MASK)
+		 >> MCDE_PID_DEVELOPMENT_VERSION_SHIFT,
+		 (pid & MCDE_PID_METALFIX_VERSION_MASK)
+		 >> MCDE_PID_METALFIX_VERSION_SHIFT);
+	if (pid != 0x03000800) {
+		dev_err(dev, "unsupported hardware revision\n");
+		ret = -ENODEV;
+		goto clk_disable;
+	}
+
+	/* Set up the main control, watermark level at 7 */
+	val = 7 << MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT;
+	/* 24 bits DPI: connect LSB Ch B to D[0:7] */
+	val |= 3 << MCDE_CONF0_OUTMUX0_SHIFT;
+	/* TV out: connect LSB Ch B to D[8:15] */
+	val |= 3 << MCDE_CONF0_OUTMUX1_SHIFT;
+	/* Don't care about this muxing */
+	val |= 0 << MCDE_CONF0_OUTMUX2_SHIFT;
+	/* 24 bits DPI: connect MID Ch B to D[24:31] */
+	val |= 4 << MCDE_CONF0_OUTMUX3_SHIFT;
+	/* 5: 24 bits DPI: connect MSB Ch B to D[32:39] */
+	val |= 5 << MCDE_CONF0_OUTMUX4_SHIFT;
+	/* Syncmux bits zero: DPI channel A and B on output pins A and B resp */
+	writel(val, mcde->regs + MCDE_CONF0);
+
+	/* Enable automatic clock gating */
+	val = readl(mcde->regs + MCDE_CR);
+	val |= MCDE_CR_MCDEEN | MCDE_CR_AUTOCLKG_EN;
+	writel(val, mcde->regs + MCDE_CR);
+
+	/* Clear any pending interrupts */
+	mcde_display_disable_irqs(mcde);
+	writel(0, mcde->regs + MCDE_IMSCERR);
+	writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR);
+
+	/* Spawn child devices for the DSI ports */
+	devm_of_platform_populate(dev);
+
+	/* Create something that will match the subdrivers when we bind */
+	for (i = 0; i < ARRAY_SIZE(mcde_component_drivers); i++) {
+		struct device_driver *drv = &mcde_component_drivers[i]->driver;
+		struct device *p = NULL, *d;
+
+		while ((d = bus_find_device(&platform_bus_type, p, drv,
+					    (void *)platform_bus_type.match))) {
+			put_device(p);
+			component_match_add(dev, &match, mcde_compare_dev, d);
+			p = d;
+		}
+		put_device(p);
+	}
+	if (IS_ERR(match)) {
+		dev_err(dev, "could not create component match\n");
+		ret = PTR_ERR(match);
+		goto clk_disable;
+	}
+	ret = component_master_add_with_match(&pdev->dev, &mcde_drm_comp_ops,
+					      match);
+	if (ret) {
+		dev_err(dev, "failed to add component master\n");
+		goto clk_disable;
+	}
+	return 0;
+
+clk_disable:
+	clk_disable_unprepare(mcde->mcde_clk);
+regulator_off:
+	regulator_disable(mcde->vana);
+regulator_epod_off:
+	regulator_disable(mcde->epod);
+dev_unref:
+	drm_dev_put(drm);
+	return ret;
+
+}
+
+static int mcde_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+	struct mcde *mcde = drm->dev_private;
+
+	component_master_del(&pdev->dev, &mcde_drm_comp_ops);
+	clk_disable_unprepare(mcde->mcde_clk);
+	regulator_disable(mcde->vana);
+	regulator_disable(mcde->epod);
+	drm_dev_put(drm);
+
+	return 0;
+}
+
+static const struct of_device_id mcde_of_match[] = {
+	{
+		.compatible = "ste,mcde",
+	},
+	{},
+};
+
+static struct platform_driver mcde_driver = {
+	.driver = {
+		.name           = "mcde",
+		.of_match_table = of_match_ptr(mcde_of_match),
+	},
+	.probe = mcde_probe,
+	.remove = mcde_remove,
+};
+
+static struct platform_driver *const component_drivers[] = {
+	&mcde_dsi_driver,
+};
+
+static int __init mcde_drm_register(void)
+{
+	int ret;
+
+	ret = platform_register_drivers(component_drivers,
+					ARRAY_SIZE(component_drivers));
+	if (ret)
+		return ret;
+
+	return platform_driver_register(&mcde_driver);
+}
+
+static void __exit mcde_drm_unregister(void)
+{
+	platform_unregister_drivers(component_drivers,
+				    ARRAY_SIZE(component_drivers));
+	platform_driver_unregister(&mcde_driver);
+}
+
+module_init(mcde_drm_register);
+module_exit(mcde_drm_unregister);
+
+MODULE_ALIAS("platform:mcde-drm");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Linus Walleij <linus.walleij at linaro.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
new file mode 100644
index 000000000000..456b43482448
--- /dev/null
+++ b/drivers/gpu/drm/mcde/mcde_dsi.c
@@ -0,0 +1,1044 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <video/mipi_display.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "mcde_drm.h"
+#include "mcde_dsi_regs.h"
+
+#define DSI_DEFAULT_LP_FREQ_HZ	19200000
+#define DSI_DEFAULT_HS_FREQ_HZ	420160000
+
+/* PRCMU DSI reset registers */
+#define PRCM_DSI_SW_RESET 0x324
+#define PRCM_DSI_SW_RESET_DSI0_SW_RESETN BIT(0)
+#define PRCM_DSI_SW_RESET_DSI1_SW_RESETN BIT(1)
+#define PRCM_DSI_SW_RESET_DSI2_SW_RESETN BIT(2)
+
+struct mcde_dsi {
+	struct device *dev;
+	struct mcde *mcde;
+	struct drm_bridge bridge;
+	struct drm_connector connector;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge_out;
+	struct mipi_dsi_host dsi_host;
+	struct mipi_dsi_device *mdsi;
+	struct clk *hs_clk;
+	struct clk *lp_clk;
+	unsigned long hs_freq;
+	unsigned long lp_freq;
+	bool unused;
+
+	void __iomem *regs;
+	struct regmap *prcmu;
+};
+
+static inline struct mcde_dsi *bridge_to_mcde_dsi(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct mcde_dsi, bridge);
+}
+
+static inline struct mcde_dsi *host_to_mcde_dsi(struct mipi_dsi_host *h)
+{
+	return container_of(h, struct mcde_dsi, dsi_host);
+}
+
+static inline struct mcde_dsi *connector_to_mcde_dsi(struct drm_connector *c)
+{
+	return container_of(c, struct mcde_dsi, connector);
+}
+
+bool mcde_dsi_irq(struct mipi_dsi_device *mdsi)
+{
+	struct mcde_dsi *d;
+	u32 val;
+	bool te_received = false;
+
+	d = host_to_mcde_dsi(mdsi->host);
+
+	dev_dbg(d->dev, "%s called\n", __func__);
+
+	val = readl(d->regs + DSI_DIRECT_CMD_STS_FLAG);
+	if (val)
+		dev_dbg(d->dev, "DSI_DIRECT_CMD_STS_FLAG = %08x\n", val);
+	if (val & DSI_DIRECT_CMD_STS_WRITE_COMPLETED)
+		dev_dbg(d->dev, "direct command write completed\n");
+	if (val & DSI_DIRECT_CMD_STS_TE_RECEIVED) {
+		te_received = true;
+		dev_dbg(d->dev, "direct command TE received\n");
+	}
+	if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED)
+		dev_err(d->dev, "direct command ACK ERR received\n");
+	if (val & DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR)
+		dev_err(d->dev, "direct command read ERR received\n");
+	/* Mask off the ACK value and clear status */
+	writel(val, d->regs + DSI_DIRECT_CMD_STS_CLR);
+
+	val = readl(d->regs + DSI_CMD_MODE_STS_FLAG);
+	if (val)
+		dev_dbg(d->dev, "DSI_CMD_MODE_STS_FLAG = %08x\n", val);
+	if (val & DSI_CMD_MODE_STS_ERR_NO_TE)
+		/* This happens all the time (safe to ignore) */
+		dev_dbg(d->dev, "CMD mode no TE\n");
+	if (val & DSI_CMD_MODE_STS_ERR_TE_MISS)
+		/* This happens all the time (safe to ignore) */
+		dev_dbg(d->dev, "CMD mode TE miss\n");
+	if (val & DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN)
+		dev_err(d->dev, "CMD mode SD1 underrun\n");
+	if (val & DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN)
+		dev_err(d->dev, "CMD mode SD2 underrun\n");
+	if (val & DSI_CMD_MODE_STS_ERR_UNWANTED_RD)
+		dev_err(d->dev, "CMD mode unwanted RD\n");
+	writel(val, d->regs + DSI_CMD_MODE_STS_CLR);
+
+	val = readl(d->regs + DSI_DIRECT_CMD_RD_STS_FLAG);
+	if (val)
+		dev_dbg(d->dev, "DSI_DIRECT_CMD_RD_STS_FLAG = %08x\n", val);
+	writel(val, d->regs + DSI_DIRECT_CMD_RD_STS_CLR);
+
+	val = readl(d->regs + DSI_TG_STS_FLAG);
+	if (val)
+		dev_dbg(d->dev, "DSI_TG_STS_FLAG = %08x\n", val);
+	writel(val, d->regs + DSI_TG_STS_CLR);
+
+	val = readl(d->regs + DSI_VID_MODE_STS_FLAG);
+	if (val)
+		dev_err(d->dev, "some video mode error status\n");
+	writel(val, d->regs + DSI_VID_MODE_STS_CLR);
+
+	return te_received;
+}
+
+static int mcde_dsi_host_attach(struct mipi_dsi_host *host,
+				struct mipi_dsi_device *mdsi)
+{
+	struct mcde_dsi *d = host_to_mcde_dsi(host);
+
+	if (mdsi->lanes < 1 || mdsi->lanes > 2) {
+		DRM_ERROR("dsi device params invalid, 1 or 2 lanes supported\n");
+		return -EINVAL;
+	}
+
+	dev_info(d->dev, "attached DSI device with %d lanes\n", mdsi->lanes);
+	/* MIPI_DSI_FMT_RGB88 etc */
+	dev_info(d->dev, "format %08x, %dbpp\n", mdsi->format,
+		 mipi_dsi_pixel_format_to_bpp(mdsi->format));
+	dev_info(d->dev, "mode flags: %08lx\n", mdsi->mode_flags);
+
+	d->mdsi = mdsi;
+	if (d->mcde)
+		d->mcde->mdsi = mdsi;
+
+	return 0;
+}
+
+static int mcde_dsi_host_detach(struct mipi_dsi_host *host,
+				struct mipi_dsi_device *mdsi)
+{
+	struct mcde_dsi *d = host_to_mcde_dsi(host);
+
+	d->mdsi = NULL;
+	if (d->mcde)
+		d->mcde->mdsi = NULL;
+
+	return 0;
+}
+
+#define MCDE_DSI_HOST_IS_READ(type)			    \
+	((type == MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) || \
+	 (type == MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM) || \
+	 (type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM) || \
+	 (type == MIPI_DSI_DCS_READ))
+
+static ssize_t mcde_dsi_host_transfer(struct mipi_dsi_host *host,
+				      const struct mipi_dsi_msg *msg)
+{
+	struct mcde_dsi *d = host_to_mcde_dsi(host);
+	const u32 loop_delay_us = 10; /* us */
+	const u8 *tx = msg->tx_buf;
+	u32 loop_counter;
+	size_t txlen;
+	u32 val;
+	int ret;
+	int i;
+
+	txlen = msg->tx_len;
+	if (txlen > 12) {
+		dev_err(d->dev,
+			"dunno how to write more than 12 bytes yet\n");
+		return -EIO;
+	}
+
+	dev_dbg(d->dev,
+		"message to channel %d, %u bytes",
+		msg->channel,
+		txlen);
+
+	/* Command "nature" */
+	if (MCDE_DSI_HOST_IS_READ(msg->type))
+		/* MCTL_MAIN_DATA_CTL already set up */
+		val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ;
+	else
+		val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE;
+	/*
+	 * More than 2 bytes will not fit in a single packet, so it's
+	 * time to set the "long not short" bit. One byte is used by
+	 * the MIPI DCS command leaving just one byte for the payload
+	 * in a short package.
+	 */
+	if (mipi_dsi_packet_format_is_long(msg->type))
+		val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT;
+	val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT;
+	/* Add one to the length for the MIPI DCS command */
+	val |= txlen
+		<< DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT;
+	val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN;
+	val |= msg->type << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT;
+	writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS);
+
+	/* MIPI DCS command is part of the data */
+	if (txlen > 0) {
+		val = 0;
+		for (i = 0; i < 4 && i < txlen; i++)
+			val |= tx[i] << (i & 3) * 8;
+	}
+	writel(val, d->regs + DSI_DIRECT_CMD_WRDAT0);
+	if (txlen > 4) {
+		val = 0;
+		for (i = 0; i < 4 && (i + 4) < txlen; i++)
+			val |= tx[i + 4] << (i & 3) * 8;
+		writel(val, d->regs + DSI_DIRECT_CMD_WRDAT1);
+	}
+	if (txlen > 8) {
+		val = 0;
+		for (i = 0; i < 4 && (i + 8) < txlen; i++)
+			val |= tx[i + 8] << (i & 3) * 8;
+		writel(val, d->regs + DSI_DIRECT_CMD_WRDAT2);
+	}
+	if (txlen > 12) {
+		val = 0;
+		for (i = 0; i < 4 && (i + 12) < txlen; i++)
+			val |= tx[i + 12] << (i & 3) * 8;
+		writel(val, d->regs + DSI_DIRECT_CMD_WRDAT3);
+	}
+
+	writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR);
+	writel(~0, d->regs + DSI_CMD_MODE_STS_CLR);
+	/* Send command */
+	writel(1, d->regs + DSI_DIRECT_CMD_SEND);
+
+	loop_counter = 1000 * 1000 / loop_delay_us;
+	while (!(readl(d->regs + DSI_DIRECT_CMD_STS) &
+		 DSI_DIRECT_CMD_STS_WRITE_COMPLETED)
+	       && --loop_counter)
+		usleep_range(loop_delay_us, (loop_delay_us * 3) / 2);
+
+	if (!loop_counter) {
+		dev_err(d->dev, "DSI write timeout!\n");
+		return -ETIME;
+	}
+
+	val = readl(d->regs + DSI_DIRECT_CMD_STS);
+	if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED) {
+		val >>= DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT;
+		dev_err(d->dev, "error during transmission: %04x\n",
+			val);
+		return -EIO;
+	}
+
+	if (!MCDE_DSI_HOST_IS_READ(msg->type)) {
+		/* Return number of bytes written */
+		if (mipi_dsi_packet_format_is_long(msg->type))
+			ret = 4 + txlen;
+		else
+			ret = 4;
+	} else {
+		/* OK this is a read command, get the response */
+		u32 rdsz;
+		u32 rddat;
+		u8 *rx = msg->rx_buf;
+
+		rdsz = readl(d->regs + DSI_DIRECT_CMD_RD_PROPERTY);
+		rdsz &= DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK;
+		rddat = readl(d->regs + DSI_DIRECT_CMD_RDDAT);
+		for (i = 0; i < 4 && i < rdsz; i++)
+			rx[i] = (rddat >> (i * 8)) & 0xff;
+		ret = rdsz;
+	}
+
+	writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR);
+	writel(~0, d->regs + DSI_CMD_MODE_STS_CLR);
+
+	return ret;
+}
+
+static const struct mipi_dsi_host_ops mcde_dsi_host_ops = {
+	.attach = mcde_dsi_host_attach,
+	.detach = mcde_dsi_host_detach,
+	.transfer = mcde_dsi_host_transfer,
+};
+
+/* This sends a direct (short) command to request TE */
+void mcde_dsi_te_request(struct mipi_dsi_device *mdsi)
+{
+	struct mcde_dsi *d;
+	u32 val;
+
+	d = host_to_mcde_dsi(mdsi->host);
+
+	/* Command "nature" TE request */
+	val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ;
+	val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT;
+	val |= 2 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT;
+	val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN;
+	val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_1 <<
+		DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT;
+	writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS);
+
+	/* Clear TE reveived and error status bits and enables them */
+	writel(DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR |
+	       DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR,
+	       d->regs + DSI_DIRECT_CMD_STS_CLR);
+	val = readl(d->regs + DSI_DIRECT_CMD_STS_CTL);
+	val |= DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN;
+	val |= DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN;
+	writel(val, d->regs + DSI_DIRECT_CMD_STS_CTL);
+
+	/* Clear and enable no TE or TE missing status */
+	writel(DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR |
+	       DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR,
+	       d->regs + DSI_CMD_MODE_STS_CLR);
+	val = readl(d->regs + DSI_CMD_MODE_STS_CTL);
+	val |= DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN;
+	val |= DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN;
+	writel(val, d->regs + DSI_CMD_MODE_STS_CTL);
+
+	/* Send this TE request command */
+	writel(1, d->regs + DSI_DIRECT_CMD_SEND);
+}
+
+static void mcde_dsi_setup_video_mode(struct mcde_dsi *d,
+				      const struct drm_display_mode *mode)
+{
+	u8 bpp = mipi_dsi_pixel_format_to_bpp(d->mdsi->format);
+	u64 bpl;
+	u32 hfp;
+	u32 hbp;
+	u32 hsa;
+	u32 blkline_pck, line_duration;
+	u32 blkeol_pck, blkeol_duration;
+	u32 val;
+
+	val = 0;
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+		val |= DSI_VID_MAIN_CTL_BURST_MODE;
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+		val |= DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE;
+		val |= DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL;
+	}
+	/* RGB header and pixel mode */
+	switch (d->mdsi->format) {
+	case MIPI_DSI_FMT_RGB565:
+		val |= MIPI_DSI_PACKED_PIXEL_STREAM_16 <<
+			DSI_VID_MAIN_CTL_HEADER_SHIFT;
+		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS;
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		val |= MIPI_DSI_PACKED_PIXEL_STREAM_18 <<
+			DSI_VID_MAIN_CTL_HEADER_SHIFT;
+		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		val |= MIPI_DSI_PIXEL_STREAM_3BYTE_18
+			<< DSI_VID_MAIN_CTL_HEADER_SHIFT;
+		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE;
+		break;
+	case MIPI_DSI_FMT_RGB888:
+		val |= MIPI_DSI_PACKED_PIXEL_STREAM_24 <<
+			DSI_VID_MAIN_CTL_HEADER_SHIFT;
+		val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS;
+		break;
+	default:
+		dev_err(d->dev, "unknown pixel mode\n");
+		return;
+	}
+
+	/* TODO: TVG could be enabled here */
+
+	/* Send blanking packet */
+	val |= DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0;
+	/* Send EOL packet */
+	val |= DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0;
+	/* Recovery mode 1 */
+	val |= 1 << DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT;
+	/* All other fields zero */
+	writel(val, d->regs + DSI_VID_MAIN_CTL);
+
+	/* Vertical frame parameters are pretty straight-forward */
+	val = mode->vdisplay << DSI_VID_VSIZE_VSA_LENGTH_SHIFT;
+	/* vertical front porch */
+	val |= (mode->vsync_start - mode->vdisplay)
+		<< DSI_VID_VSIZE_VFP_LENGTH_SHIFT;
+	/* vertical sync active */
+	val |= (mode->vsync_end - mode->vsync_start)
+		<< DSI_VID_VSIZE_VACT_LENGTH_SHIFT;
+	/* vertical back porch */
+	val |= (mode->vtotal - mode->vsync_end)
+		<< DSI_VID_VSIZE_VBP_LENGTH_SHIFT;
+	writel(val, d->regs + DSI_VID_VSIZE);
+
+	/*
+	 * Horizontal frame parameters:
+	 * horizontal resolution is given in pixels and must be re-calculated
+	 * into bytes since this is what the hardware expects.
+	 *
+	 * 6 + 2 is HFP header + checksum
+	 */
+	hfp = (mode->hsync_start - mode->hdisplay) * bpp - 6 - 2;
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+		/*
+		 * 6 is HBP header + checksum
+		 * 4 is RGB header + checksum
+		 */
+		hbp = (mode->htotal - mode->hsync_end) * bpp - 4 - 6;
+		/*
+		 * 6 is HBP header + checksum
+		 * 4 is HSW packet bytes
+		 * 4 is RGB header + checksum
+		 */
+		hsa = (mode->hsync_end - mode->hsync_start) * bpp - 4 - 4 - 6;
+	} else {
+		/*
+		 * HBP includes both back porch and sync
+		 * 6 is HBP header + checksum
+		 * 4 is HSW packet bytes
+		 * 4 is RGB header + checksum
+		 */
+		hbp = (mode->htotal - mode->hsync_start) * bpp - 4 - 4 - 6;
+		/* HSA is not considered in this mode and set to 0 */
+		hsa = 0;
+	}
+	dev_dbg(d->dev, "hfp: %u, hbp: %u, hsa: %u\n",
+		hfp, hbp, hsa);
+
+	/* Frame parameters: horizontal sync active */
+	val = hsa << DSI_VID_HSIZE1_HSA_LENGTH_SHIFT;
+	/* horizontal back porch */
+	val |= hbp << DSI_VID_HSIZE1_HBP_LENGTH_SHIFT;
+	/* horizontal front porch */
+	val |= hfp << DSI_VID_HSIZE1_HFP_LENGTH_SHIFT;
+	writel(val, d->regs + DSI_VID_HSIZE1);
+
+	/* RGB data length (bytes on one scanline) */
+	val = mode->hdisplay * (bpp / 8);
+	writel(val, d->regs + DSI_VID_HSIZE2);
+
+	/* TODO: further adjustments for TVG mode here */
+
+	/*
+	 * EOL packet length from bits per line calculations: pixel clock
+	 * is given in kHz, calculate the time between two pixels in
+	 * picoseconds.
+	 */
+	bpl = mode->clock * mode->htotal;
+	bpl *= (d->hs_freq / 8);
+	do_div(bpl, 1000000); /* microseconds */
+	do_div(bpl, 1000000); /* seconds */
+	bpl *= d->mdsi->lanes;
+	dev_dbg(d->dev, "calculated bytes per line: %llu\n", bpl);
+	/*
+	 * 6 is header + checksum, header = 4 bytes, checksum = 2 bytes
+	 * 4 is short packet for vsync/hsync
+	 */
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+		/* Fixme: isn't the hsync width in pixels? */
+		blkline_pck = bpl - (mode->hsync_end - mode->hsync_start) - 6;
+		val = blkline_pck << DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT;
+		writel(val, d->regs + DSI_VID_BLKSIZE2);
+	} else {
+		blkline_pck = bpl - 4 - 6;
+		val = blkline_pck << DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT;
+		writel(val, d->regs + DSI_VID_BLKSIZE1);
+	}
+
+	line_duration = (blkline_pck + 6) / d->mdsi->lanes;
+	dev_dbg(d->dev, "line duration %u\n", line_duration);
+	val = line_duration << DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT;
+	/*
+	 * This is the time to perform LP->HS on D-PHY
+	 * FIXME: nowhere to get this from: DT property on the DSI?
+	 */
+	val |= 0 << DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT;
+	writel(val, d->regs + DSI_VID_DPHY_TIME);
+
+	/* Calculate block end of line */
+	blkeol_pck = bpl - mode->hdisplay * bpp - 6;
+	blkeol_duration = (blkeol_pck + 6) / d->mdsi->lanes;
+	dev_dbg(d->dev, "blkeol pck: %u, duration: %u\n",
+		 blkeol_pck, blkeol_duration);
+
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) {
+		/* Set up EOL clock for burst mode */
+		val = readl(d->regs + DSI_VID_BLKSIZE1);
+		val |= blkeol_pck << DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT;
+		writel(val, d->regs + DSI_VID_BLKSIZE1);
+		writel(blkeol_pck, d->regs + DSI_VID_VCA_SETTING2);
+
+		writel(blkeol_duration, d->regs + DSI_VID_PCK_TIME);
+		writel(blkeol_duration - 6, d->regs + DSI_VID_VCA_SETTING1);
+	}
+
+	/* Maximum line limit */
+	val = readl(d->regs + DSI_VID_VCA_SETTING2);
+	val |= blkline_pck <<
+		DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT;
+	writel(val, d->regs + DSI_VID_VCA_SETTING2);
+
+	/* Put IF1 into video mode */
+	val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
+	val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE;
+	writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
+
+	/* Disable command mode on IF1 */
+	val = readl(d->regs + DSI_CMD_MODE_CTL);
+	val &= ~DSI_CMD_MODE_CTL_IF1_LP_EN;
+	writel(val, d->regs + DSI_CMD_MODE_CTL);
+
+	/* Enable some error interrupts */
+	val = readl(d->regs + DSI_VID_MODE_STS_CTL);
+	val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC;
+	val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA;
+	writel(val, d->regs + DSI_VID_MODE_STS_CTL);
+
+	/* Enable video mode */
+	val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
+	val |= DSI_MCTL_MAIN_DATA_CTL_VID_EN;
+	writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
+}
+
+static void mcde_dsi_start(struct mcde_dsi *d)
+{
+	unsigned long hs_freq;
+	u32 val;
+	int i;
+
+	/* No integration mode */
+	writel(0, d->regs + DSI_MCTL_INTEGRATION_MODE);
+
+	/* Enable the DSI port, from drivers/video/mcde/dsilink_v2.c */
+	val = DSI_MCTL_MAIN_DATA_CTL_LINK_EN |
+		DSI_MCTL_MAIN_DATA_CTL_BTA_EN |
+		DSI_MCTL_MAIN_DATA_CTL_READ_EN |
+		DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN;
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
+		val |= DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN;
+	writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
+
+	/* Set a high command timeout, clear other fields */
+	val = 0x3ff << DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT;
+	writel(val, d->regs + DSI_CMD_MODE_CTL);
+
+	/*
+	 * UI_X4 is described as "unit interval times four"
+	 * I guess since DSI packets are 4 bytes wide, one unit
+	 * is one byte.
+	 */
+	hs_freq = clk_get_rate(d->hs_clk);
+	hs_freq /= 1000000; /* MHz */
+	val = 4000 / hs_freq;
+	dev_dbg(d->dev, "UI value: %d\n", val);
+	val <<= DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT;
+	val &= DSI_MCTL_DPHY_STATIC_UI_X4_MASK;
+	writel(val, d->regs + DSI_MCTL_DPHY_STATIC);
+
+	/*
+	 * Enable clocking: 0x0f (something?) between each burst,
+	 * enable the second lane if needed, enable continuous clock if
+	 * needed, enable switch into ULPM (ultra-low power mode) on
+	 * all the lines.
+	 */
+	val = 0x0f << DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT;
+	if (d->mdsi->lanes == 2)
+		val |= DSI_MCTL_MAIN_PHY_CTL_LANE2_EN;
+	if (!(d->mdsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+		val |= DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS;
+	val |= DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN |
+		DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN |
+		DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN;
+	writel(val, d->regs + DSI_MCTL_MAIN_PHY_CTL);
+
+	val = (1 << DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT) |
+		(1 << DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT);
+	writel(val, d->regs + DSI_MCTL_ULPOUT_TIME);
+
+	writel(DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90,
+	       d->regs + DSI_DPHY_LANES_TRIM);
+
+	/* High PHY timeout */
+	val = (0x0f << DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT) |
+		(0x3fff << DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT) |
+		(0x3fff << DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT);
+	writel(val, d->regs + DSI_MCTL_DPHY_TIMEOUT);
+
+	val = DSI_MCTL_MAIN_EN_PLL_START |
+		DSI_MCTL_MAIN_EN_CKLANE_EN |
+		DSI_MCTL_MAIN_EN_DAT1_EN |
+		DSI_MCTL_MAIN_EN_IF1_EN;
+	if (d->mdsi->lanes == 2)
+		val |= DSI_MCTL_MAIN_EN_DAT2_EN;
+	writel(val, d->regs + DSI_MCTL_MAIN_EN);
+
+	/* Wait for the PLL to lock and the clock and data lines to come up */
+	i = 0;
+	val = DSI_MCTL_MAIN_STS_PLL_LOCK |
+		DSI_MCTL_MAIN_STS_CLKLANE_READY |
+		DSI_MCTL_MAIN_STS_DAT1_READY;
+	if (d->mdsi->lanes == 2)
+		val |= DSI_MCTL_MAIN_STS_DAT2_READY;
+	while ((readl(d->regs + DSI_MCTL_MAIN_STS) & val) != val) {
+		/* Sleep for a millisecond */
+		usleep_range(1000, 1500);
+		if (i++ == 100) {
+			dev_warn(d->dev, "DSI lanes did not start up\n");
+			return;
+		}
+	}
+
+	/* TODO needed? */
+
+	/* Command mode, clear IF1 ID */
+	val = readl(d->regs + DSI_CMD_MODE_CTL);
+	/*
+	 * If we enable low-power mode here, with
+	 * val |= DSI_CMD_MODE_CTL_IF1_LP_EN
+	 * then display updates become really slow.
+	 */
+	val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK;
+	writel(val, d->regs + DSI_CMD_MODE_CTL);
+
+	/* Wait for DSI PHY to initialize */
+	usleep_range(100, 200);
+	dev_info(d->dev, "DSI link enabled\n");
+}
+
+
+static void mcde_dsi_bridge_enable(struct drm_bridge *bridge)
+{
+	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
+
+	dev_info(d->dev, "enable DSI master\n");
+};
+
+static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge,
+				     const struct drm_display_mode *mode,
+				     const struct drm_display_mode *adj)
+{
+	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
+	unsigned long pixel_clock_hz = mode->clock * 1000;
+	unsigned long hs_freq, lp_freq;
+	u32 val;
+	int ret;
+
+	if (!d->mdsi) {
+		dev_err(d->dev, "no DSI device attached to encoder!\n");
+		return;
+	}
+
+	dev_info(d->dev, "set DSI master to %dx%d %lu Hz %s mode\n",
+		 mode->hdisplay, mode->vdisplay, pixel_clock_hz,
+		 (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD"
+		);
+
+	/* Copy maximum clock frequencies */
+	if (d->mdsi->lp_rate)
+		lp_freq = d->mdsi->lp_rate;
+	else
+		lp_freq = DSI_DEFAULT_LP_FREQ_HZ;
+	if (d->mdsi->hs_rate)
+		hs_freq = d->mdsi->hs_rate;
+	else
+		hs_freq = DSI_DEFAULT_HS_FREQ_HZ;
+
+	/* Enable LP (Low Power, Energy Save, ES) and HS (High Speed) clocks */
+	d->lp_freq = clk_round_rate(d->lp_clk, lp_freq);
+	ret = clk_set_rate(d->lp_clk, d->lp_freq);
+	if (ret)
+		dev_err(d->dev, "failed to set LP clock rate %lu Hz\n",
+			d->lp_freq);
+
+	d->hs_freq = clk_round_rate(d->hs_clk, hs_freq);
+	ret = clk_set_rate(d->hs_clk, d->hs_freq);
+	if (ret)
+		dev_err(d->dev, "failed to set HS clock rate %lu Hz\n",
+			d->hs_freq);
+
+	/* Start clocks */
+	ret = clk_prepare_enable(d->lp_clk);
+	if (ret)
+		dev_err(d->dev, "failed to enable LP clock\n");
+	else
+		dev_info(d->dev, "DSI LP clock rate %lu Hz\n",
+			 d->lp_freq);
+	ret = clk_prepare_enable(d->hs_clk);
+	if (ret)
+		dev_err(d->dev, "failed to enable HS clock\n");
+	else
+		dev_info(d->dev, "DSI HS clock rate %lu Hz\n",
+			 d->hs_freq);
+
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
+		mcde_dsi_setup_video_mode(d, mode);
+	} else {
+		/* Command mode, clear IF1 ID */
+		val = readl(d->regs + DSI_CMD_MODE_CTL);
+		/*
+		 * If we enable low-power mode here with
+		 * val |= DSI_CMD_MODE_CTL_IF1_LP_EN
+		 * the display updates become really slow.
+		 */
+		val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK;
+		writel(val, d->regs + DSI_CMD_MODE_CTL);
+	}
+}
+
+static void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d)
+{
+	u32 val;
+	int i;
+
+	/*
+	 * Wait until we get out of command mode
+	 * CSM = Command State Machine
+	 */
+	i = 0;
+	val = DSI_CMD_MODE_STS_CSM_RUNNING;
+	while ((readl(d->regs + DSI_CMD_MODE_STS) & val) == val) {
+		/* Sleep for a millisecond */
+		usleep_range(1000, 2000);
+		if (i++ == 100) {
+			dev_warn(d->dev,
+				 "could not get out of command mode\n");
+			return;
+		}
+	}
+}
+
+static void mcde_dsi_wait_for_video_mode_stop(struct mcde_dsi *d)
+{
+	u32 val;
+	int i;
+
+	/* Wait until we get out og video mode */
+	i = 0;
+	val = DSI_VID_MODE_STS_VSG_RUNNING;
+	while ((readl(d->regs + DSI_VID_MODE_STS) & val) == val) {
+		/* Sleep for a millisecond */
+		usleep_range(1000, 2000);
+		if (i++ == 100) {
+			dev_warn(d->dev,
+				 "could not get out of video mode\n");
+			return;
+		}
+	}
+}
+
+static void mcde_dsi_bridge_disable(struct drm_bridge *bridge)
+{
+	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
+	u32 val;
+
+	/* Disable all error interrupts */
+	writel(0, d->regs + DSI_VID_MODE_STS_CTL);
+
+	if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
+		/* Stop video mode */
+		val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL);
+		val &= ~DSI_MCTL_MAIN_DATA_CTL_VID_EN;
+		writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL);
+		mcde_dsi_wait_for_video_mode_stop(d);
+	} else {
+		/* Stop command mode */
+		mcde_dsi_wait_for_command_mode_stop(d);
+	}
+
+	/* Stop clocks */
+	clk_disable_unprepare(d->hs_clk);
+	clk_disable_unprepare(d->lp_clk);
+}
+
+/*
+ * This connector needs no special handling, just use the default
+ * helpers for everything. It's pretty dummy.
+ */
+static const struct drm_connector_funcs mcde_dsi_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int mcde_dsi_get_modes(struct drm_connector *connector)
+{
+	struct mcde_dsi *d = connector_to_mcde_dsi(connector);
+
+	/* Just pass the question to the panel */
+	if (d->panel)
+		return drm_panel_get_modes(d->panel);
+
+	/* TODO: deal with bridges */
+
+	return 0;
+}
+
+static const struct drm_connector_helper_funcs
+mcde_dsi_connector_helper_funcs = {
+	.get_modes = mcde_dsi_get_modes,
+};
+
+static int mcde_dsi_bridge_attach(struct drm_bridge *bridge)
+{
+	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
+	struct drm_device *drm = bridge->dev;
+	int ret;
+
+	drm_connector_helper_add(&d->connector,
+				 &mcde_dsi_connector_helper_funcs);
+
+	if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) {
+		dev_err(d->dev, "we need atomic updates\n");
+		return -ENOTSUPP;
+	}
+
+	ret = drm_connector_init(drm, &d->connector,
+				 &mcde_dsi_connector_funcs,
+				 DRM_MODE_CONNECTOR_DSI);
+	if (ret) {
+		dev_err(d->dev, "failed to initialize DSI bridge connector\n");
+		return ret;
+	}
+	d->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
+	/* The encoder in the bridge attached to the DSI bridge */
+	drm_connector_attach_encoder(&d->connector, bridge->encoder);
+	/* Then we attach the DSI bridge to the output (panel etc) bridge */
+	ret = drm_bridge_attach(bridge->encoder, d->bridge_out, bridge);
+	if (ret) {
+		dev_err(d->dev, "failed to attach the DSI bridge\n");
+		return ret;
+	}
+	d->connector.status = connector_status_connected;
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs mcde_dsi_bridge_funcs = {
+	.attach = mcde_dsi_bridge_attach,
+	.mode_set = mcde_dsi_bridge_mode_set,
+	.disable = mcde_dsi_bridge_disable,
+	.enable = mcde_dsi_bridge_enable,
+};
+
+static int mcde_dsi_bind(struct device *dev, struct device *master,
+			 void *data)
+{
+	struct drm_device *drm = data;
+	struct mcde *mcde = drm->dev_private;
+	struct mcde_dsi *d = dev_get_drvdata(dev);
+	struct device_node *child;
+	struct drm_panel *panel = NULL;
+	struct drm_bridge *bridge = NULL;
+
+	if (!of_get_available_child_count(dev->of_node)) {
+		dev_info(dev, "unused DSI interface\n");
+		d->unused = true;
+		return 0;
+	}
+	d->mcde = mcde;
+	/* If the display attached before binding, set this up */
+	if (d->mdsi)
+		d->mcde->mdsi = d->mdsi;
+
+	/* Obtain the clocks */
+	d->hs_clk = devm_clk_get(dev, "hs");
+	if (IS_ERR(d->hs_clk)) {
+		dev_err(dev, "unable to get HS clock\n");
+		return PTR_ERR(d->hs_clk);
+	}
+
+	d->lp_clk = devm_clk_get(dev, "lp");
+	if (IS_ERR(d->lp_clk)) {
+		dev_err(dev, "unable to get LP clock\n");
+		return PTR_ERR(d->lp_clk);
+	}
+
+	/* Assert RESET through the PRCMU, active low */
+	/* FIXME: which DSI block? */
+	regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
+			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
+
+	usleep_range(100, 200);
+
+	/* De-assert RESET again */
+	regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
+			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN,
+			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN);
+
+	/* Start up the hardware */
+	mcde_dsi_start(d);
+
+	/* Look for a panel as a child to this node */
+	for_each_available_child_of_node(dev->of_node, child) {
+		panel = of_drm_find_panel(child);
+		if (IS_ERR(panel)) {
+			dev_err(dev, "failed to find panel try bridge (%lu)\n",
+				PTR_ERR(panel));
+			bridge = of_drm_find_bridge(child);
+			if (IS_ERR(bridge)) {
+				dev_err(dev, "failed to find bridge (%lu)\n",
+					PTR_ERR(bridge));
+				return PTR_ERR(bridge);
+			}
+		}
+	}
+	if (panel) {
+		bridge = drm_panel_bridge_add(panel,
+					      DRM_MODE_CONNECTOR_DSI);
+		if (IS_ERR(bridge)) {
+			dev_err(dev, "error adding panel bridge\n");
+			return PTR_ERR(bridge);
+		}
+		dev_info(dev, "connected to panel\n");
+		d->panel = panel;
+	} else if (bridge) {
+		/* TODO: AV8100 HDMI encoder goes here for example */
+		dev_info(dev, "connected to non-panel bridge (unsupported)\n");
+		return -ENODEV;
+	} else {
+		dev_err(dev, "no panel or bridge\n");
+		return -ENODEV;
+	}
+
+	d->bridge_out = bridge;
+
+	/* Create a bridge for this DSI channel */
+	d->bridge.funcs = &mcde_dsi_bridge_funcs;
+	d->bridge.of_node = dev->of_node;
+	drm_bridge_add(&d->bridge);
+
+	/* TODO: first come first serve, use a list */
+	mcde->bridge = &d->bridge;
+
+	dev_info(dev, "initialized MCDE DSI bridge\n");
+
+	return 0;
+}
+
+static void mcde_dsi_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct mcde_dsi *d = dev_get_drvdata(dev);
+
+	if (d->panel)
+		drm_panel_bridge_remove(d->bridge_out);
+	regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
+			   PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
+}
+
+static const struct component_ops mcde_dsi_component_ops = {
+	.bind   = mcde_dsi_bind,
+	.unbind = mcde_dsi_unbind,
+};
+
+static int mcde_dsi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mcde_dsi *d;
+	struct mipi_dsi_host *host;
+	struct resource *res;
+	u32 dsi_id;
+	int ret;
+
+	d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+	d->dev = dev;
+	platform_set_drvdata(pdev, d);
+
+	/* Get a handle on the PRCMU so we can do reset */
+	d->prcmu =
+		syscon_regmap_lookup_by_compatible("stericsson,db8500-prcmu");
+	if (IS_ERR(d->prcmu)) {
+		dev_err(dev, "no PRCMU regmap\n");
+		return PTR_ERR(d->prcmu);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	d->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(d->regs)) {
+		dev_err(dev, "no DSI regs\n");
+		return PTR_ERR(d->regs);
+	}
+
+	dsi_id = readl(d->regs + DSI_ID_REG);
+	dev_info(dev, "HW revision 0x%08x\n", dsi_id);
+
+	host = &d->dsi_host;
+	host->dev = dev;
+	host->ops = &mcde_dsi_host_ops;
+	ret = mipi_dsi_host_register(host);
+	if (ret < 0) {
+		dev_err(dev, "failed to register DSI host: %d\n", ret);
+		return ret;
+	}
+	dev_info(dev, "registered DSI host\n");
+
+	platform_set_drvdata(pdev, d);
+	return component_add(dev, &mcde_dsi_component_ops);
+}
+
+static int mcde_dsi_remove(struct platform_device *pdev)
+{
+	struct mcde_dsi *d = platform_get_drvdata(pdev);
+
+	component_del(&pdev->dev, &mcde_dsi_component_ops);
+	mipi_dsi_host_unregister(&d->dsi_host);
+
+	return 0;
+}
+
+static const struct of_device_id mcde_dsi_of_match[] = {
+	{
+		.compatible = "ste,mcde-dsi",
+	},
+	{},
+};
+
+struct platform_driver mcde_dsi_driver = {
+	.driver = {
+		.name           = "mcde-dsi",
+		.of_match_table = of_match_ptr(mcde_dsi_of_match),
+	},
+	.probe = mcde_dsi_probe,
+	.remove = mcde_dsi_remove,
+};
diff --git a/drivers/gpu/drm/mcde/mcde_dsi_regs.h b/drivers/gpu/drm/mcde/mcde_dsi_regs.h
new file mode 100644
index 000000000000..c9253321a3be
--- /dev/null
+++ b/drivers/gpu/drm/mcde/mcde_dsi_regs.h
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DRM_MCDE_DSI_REGS
+#define __DRM_MCDE_DSI_REGS
+
+#define DSI_MCTL_INTEGRATION_MODE 0x00000000
+
+#define DSI_MCTL_MAIN_DATA_CTL 0x00000004
+#define DSI_MCTL_MAIN_DATA_CTL_LINK_EN BIT(0)
+#define DSI_MCTL_MAIN_DATA_CTL_IF1_MODE BIT(1)
+#define DSI_MCTL_MAIN_DATA_CTL_VID_EN BIT(2)
+#define DSI_MCTL_MAIN_DATA_CTL_TVG_SEL BIT(3)
+#define DSI_MCTL_MAIN_DATA_CTL_TBG_SEL BIT(4)
+#define DSI_MCTL_MAIN_DATA_CTL_IF1_TE_EN BIT(5)
+#define DSI_MCTL_MAIN_DATA_CTL_IF2_TE_EN BIT(6)
+#define DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN BIT(7)
+#define DSI_MCTL_MAIN_DATA_CTL_READ_EN BIT(8)
+#define DSI_MCTL_MAIN_DATA_CTL_BTA_EN BIT(9)
+#define DSI_MCTL_MAIN_DATA_CTL_DISP_GEN_ECC BIT(10)
+#define DSI_MCTL_MAIN_DATA_CTL_DISP_GEN_CHECKSUM BIT(11)
+#define DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN BIT(12)
+#define DSI_MCTL_MAIN_DATA_CTL_DISP_EOT_GEN BIT(13)
+#define DSI_MCTL_MAIN_DATA_CTL_DLX_REMAP_EN BIT(14)
+#define DSI_MCTL_MAIN_DATA_CTL_TE_POLLING_EN BIT(15)
+
+#define DSI_MCTL_MAIN_PHY_CTL 0x00000008
+#define DSI_MCTL_MAIN_PHY_CTL_LANE2_EN BIT(0)
+#define DSI_MCTL_MAIN_PHY_CTL_FORCE_STOP_MODE BIT(1)
+#define DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS BIT(2)
+#define DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN BIT(3)
+#define DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN BIT(4)
+#define DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN BIT(5)
+#define DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT 6
+#define DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_MASK 0x000003C0
+#define DSI_MCTL_MAIN_PHY_CTL_CLOCK_FORCE_STOP_MODE BIT(10)
+
+#define DSI_MCTL_PLL_CTL 0x0000000C
+#define DSI_MCTL_LANE_STS 0x00000010
+
+#define DSI_MCTL_DPHY_TIMEOUT 0x00000014
+#define DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT 0
+#define DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_MASK 0x0000000F
+#define DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT 4
+#define DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_MASK 0x0003FFF0
+#define DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT 18
+#define DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_MASK 0xFFFC0000
+
+#define DSI_MCTL_ULPOUT_TIME 0x00000018
+#define DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT 0
+#define DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_MASK 0x000001FF
+#define DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT 9
+#define DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_MASK 0x0003FE00
+
+#define DSI_MCTL_DPHY_STATIC 0x0000001C
+#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_CLK BIT(0)
+#define DSI_MCTL_DPHY_STATIC_HS_INVERT_CLK BIT(1)
+#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_DAT1 BIT(2)
+#define DSI_MCTL_DPHY_STATIC_HS_INVERT_DAT1 BIT(3)
+#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_DAT2 BIT(4)
+#define DSI_MCTL_DPHY_STATIC_HS_INVERT_DAT2 BIT(5)
+#define DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT 6
+#define DSI_MCTL_DPHY_STATIC_UI_X4_MASK 0x00000FC0
+
+#define DSI_MCTL_MAIN_EN 0x00000020
+#define DSI_MCTL_MAIN_EN_PLL_START BIT(0)
+#define DSI_MCTL_MAIN_EN_CKLANE_EN BIT(3)
+#define DSI_MCTL_MAIN_EN_DAT1_EN BIT(4)
+#define DSI_MCTL_MAIN_EN_DAT2_EN BIT(5)
+#define DSI_MCTL_MAIN_EN_CLKLANE_ULPM_REQ BIT(6)
+#define DSI_MCTL_MAIN_EN_DAT1_ULPM_REQ BIT(7)
+#define DSI_MCTL_MAIN_EN_DAT2_ULPM_REQ BIT(8)
+#define DSI_MCTL_MAIN_EN_IF1_EN BIT(9)
+#define DSI_MCTL_MAIN_EN_IF2_EN BIT(10)
+
+#define DSI_MCTL_MAIN_STS 0x00000024
+#define DSI_MCTL_MAIN_STS_PLL_LOCK BIT(0)
+#define DSI_MCTL_MAIN_STS_CLKLANE_READY BIT(1)
+#define DSI_MCTL_MAIN_STS_DAT1_READY BIT(2)
+#define DSI_MCTL_MAIN_STS_DAT2_READY BIT(3)
+#define DSI_MCTL_MAIN_STS_HSTX_TO_ERR BIT(4)
+#define DSI_MCTL_MAIN_STS_LPRX_TO_ERR BIT(5)
+#define DSI_MCTL_MAIN_STS_CRS_UNTERM_PCK BIT(6)
+#define DSI_MCTL_MAIN_STS_VRS_UNTERM_PCK BIT(7)
+
+#define DSI_MCTL_DPHY_ERR 0x00000028
+#define DSI_INT_VID_RDDATA 0x00000030
+#define DSI_INT_VID_GNT 0x00000034
+#define DSI_INT_CMD_RDDATA 0x00000038
+#define DSI_INT_CMD_GNT 0x0000003C
+#define DSI_INT_INTERRUPT_CTL 0x00000040
+
+#define DSI_CMD_MODE_CTL 0x00000050
+#define DSI_CMD_MODE_CTL_IF1_ID_SHIFT 0
+#define DSI_CMD_MODE_CTL_IF1_ID_MASK 0x00000003
+#define DSI_CMD_MODE_CTL_IF2_ID_SHIFT 2
+#define DSI_CMD_MODE_CTL_IF2_ID_MASK 0x0000000C
+#define DSI_CMD_MODE_CTL_IF1_LP_EN BIT(4)
+#define DSI_CMD_MODE_CTL_IF2_LP_EN BIT(5)
+#define DSI_CMD_MODE_CTL_ARB_MODE BIT(6)
+#define DSI_CMD_MODE_CTL_ARB_PRI BIT(7)
+#define DSI_CMD_MODE_CTL_FIL_VALUE_SHIFT 8
+#define DSI_CMD_MODE_CTL_FIL_VALUE_MASK 0x0000FF00
+#define DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT 16
+#define DSI_CMD_MODE_CTL_TE_TIMEOUT_MASK 0x03FF0000
+
+#define DSI_CMD_MODE_STS 0x00000054
+#define DSI_CMD_MODE_STS_ERR_NO_TE BIT(0)
+#define DSI_CMD_MODE_STS_ERR_TE_MISS BIT(1)
+#define DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN BIT(2)
+#define DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN BIT(3)
+#define DSI_CMD_MODE_STS_ERR_UNWANTED_RD BIT(4)
+#define DSI_CMD_MODE_STS_CSM_RUNNING BIT(5)
+
+#define DSI_DIRECT_CMD_SEND 0x00000060
+
+#define DSI_DIRECT_CMD_MAIN_SETTINGS 0x00000064
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_SHIFT 0
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_MASK 0x00000007
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE 0
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ 1
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ 4
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TRIG_REQ 5
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_BTA_REQ 6
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT BIT(3)
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT 8
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_MASK 0x00003F00
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_TURN_ON_PERIPHERAL 50
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHUT_DOWN_PERIPHERAL 34
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_0 3
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_1 19
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_2 35
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_LONG_WRITE 41
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_0 5
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_1 21
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_LONG_WRITE 57
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_READ 6
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SET_MAX_PKT_SIZE 55
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT 14
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT 16
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN BIT(21)
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_TRIGGER_VAL_SHIFT 24
+#define DSI_DIRECT_CMD_MAIN_SETTINGS_TRIGGER_VAL_MASK 0x0F000000
+
+#define DSI_DIRECT_CMD_STS 0x00000068
+#define DSI_DIRECT_CMD_STS_CMD_TRANSMISSION BIT(0)
+#define DSI_DIRECT_CMD_STS_WRITE_COMPLETED BIT(1)
+#define DSI_DIRECT_CMD_STS_TRIGGER_COMPLETED BIT(2)
+#define DSI_DIRECT_CMD_STS_READ_COMPLETED BIT(3)
+#define DSI_DIRECT_CMD_STS_ACKNOWLEDGE_RECEIVED_SHIFT BIT(4)
+#define DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED BIT(5)
+#define DSI_DIRECT_CMD_STS_TRIGGER_RECEIVED BIT(6)
+#define DSI_DIRECT_CMD_STS_TE_RECEIVED BIT(7)
+#define DSI_DIRECT_CMD_STS_BTA_COMPLETED BIT(8)
+#define DSI_DIRECT_CMD_STS_BTA_FINISHED BIT(9)
+#define DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR BIT(10)
+#define DSI_DIRECT_CMD_STS_TRIGGER_VAL_MASK 0x00007800
+#define DSI_DIRECT_CMD_STS_TRIGGER_VAL_SHIFT 11
+#define DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT 16
+#define DSI_DIRECT_CMD_STS_ACK_VAL_MASK 0xFFFF0000
+
+#define DSI_DIRECT_CMD_RD_INIT 0x0000006C
+#define DSI_DIRECT_CMD_RD_INIT_RESET_SHIFT 0
+#define DSI_DIRECT_CMD_RD_INIT_RESET_MASK 0xFFFFFFFF
+
+#define DSI_DIRECT_CMD_WRDAT0 0x00000070
+#define DSI_DIRECT_CMD_WRDAT1 0x00000074
+#define DSI_DIRECT_CMD_WRDAT2 0x00000078
+#define DSI_DIRECT_CMD_WRDAT3 0x0000007C
+
+#define DSI_DIRECT_CMD_RDDAT 0x00000080
+
+#define DSI_DIRECT_CMD_RD_PROPERTY 0x00000084
+#define DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_SHIFT 0
+#define DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK 0x0000FFFF
+#define DSI_DIRECT_CMD_RD_PROPERTY_RD_ID_SHIFT 16
+#define DSI_DIRECT_CMD_RD_PROPERTY_RD_ID_MASK 0x00030000
+#define DSI_DIRECT_CMD_RD_PROPERTY_RD_DCSNOTGENERIC_SHIFT 18
+#define DSI_DIRECT_CMD_RD_PROPERTY_RD_DCSNOTGENERIC_MASK 0x00040000
+
+#define DSI_DIRECT_CMD_RD_STS 0x00000088
+
+#define DSI_VID_MAIN_CTL 0x00000090
+#define DSI_VID_MAIN_CTL_START_MODE_SHIFT 0
+#define DSI_VID_MAIN_CTL_START_MODE_MASK 0x00000003
+#define DSI_VID_MAIN_CTL_STOP_MODE_SHIFT 2
+#define DSI_VID_MAIN_CTL_STOP_MODE_MASK 0x0000000C
+#define DSI_VID_MAIN_CTL_VID_ID_SHIFT 4
+#define DSI_VID_MAIN_CTL_VID_ID_MASK 0x00000030
+#define DSI_VID_MAIN_CTL_HEADER_SHIFT 6
+#define DSI_VID_MAIN_CTL_HEADER_MASK 0x00000FC0
+#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS 0
+#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS BIT(12)
+#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE BIT(13)
+#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS (BIT(12) | BIT(13))
+#define DSI_VID_MAIN_CTL_BURST_MODE BIT(14)
+#define DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE BIT(15)
+#define DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL BIT(16)
+#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_NULL 0
+#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_BLANKING BIT(17)
+#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0 BIT(18)
+#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_1 (BIT(17) | BIT(18))
+#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_NULL 0
+#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_BLANKING BIT(19)
+#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 BIT(20)
+#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_1 (BIT(19) | BIT(20))
+#define DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT 21
+#define DSI_VID_MAIN_CTL_RECOVERY_MODE_MASK 0x00600000
+
+#define DSI_VID_VSIZE 0x00000094
+#define DSI_VID_VSIZE_VSA_LENGTH_SHIFT 0
+#define DSI_VID_VSIZE_VSA_LENGTH_MASK 0x0000003F
+#define DSI_VID_VSIZE_VBP_LENGTH_SHIFT 6
+#define DSI_VID_VSIZE_VBP_LENGTH_MASK 0x00000FC0
+#define DSI_VID_VSIZE_VFP_LENGTH_SHIFT 12
+#define DSI_VID_VSIZE_VFP_LENGTH_MASK 0x000FF000
+#define DSI_VID_VSIZE_VACT_LENGTH_SHIFT 20
+#define DSI_VID_VSIZE_VACT_LENGTH_MASK 0x7FF00000
+
+#define DSI_VID_HSIZE1 0x00000098
+#define DSI_VID_HSIZE1_HSA_LENGTH_SHIFT 0
+#define DSI_VID_HSIZE1_HSA_LENGTH_MASK 0x000003FF
+#define DSI_VID_HSIZE1_HBP_LENGTH_SHIFT 10
+#define DSI_VID_HSIZE1_HBP_LENGTH_MASK 0x000FFC00
+#define DSI_VID_HSIZE1_HFP_LENGTH_SHIFT 20
+#define DSI_VID_HSIZE1_HFP_LENGTH_MASK 0x7FF00000
+
+#define DSI_VID_HSIZE2 0x0000009C
+#define DSI_VID_HSIZE2_RGB_SIZE_SHIFT 0
+#define DSI_VID_HSIZE2_RGB_SIZE_MASK 0x00001FFF
+
+#define DSI_VID_BLKSIZE1 0x000000A0
+#define DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT 0
+#define DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_MASK 0x00001FFF
+#define DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT 13
+#define DSI_VID_BLKSIZE1_BLKEOL_PCK_MASK 0x03FFE000
+
+#define DSI_VID_BLKSIZE2 0x000000A4
+#define DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT 0
+#define DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_MASK 0x00001FFF
+
+#define DSI_VID_PCK_TIME 0x000000A8
+#define DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT 0
+
+#define DSI_VID_DPHY_TIME 0x000000AC
+#define DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT 0
+#define DSI_VID_DPHY_TIME_REG_LINE_DURATION_MASK 0x00001FFF
+#define DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT 13
+#define DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_MASK 0x00FFE000
+
+#define DSI_VID_MODE_STS 0x000000BC
+#define DSI_VID_MODE_STS_VSG_RUNNING BIT(0)
+
+#define DSI_VID_VCA_SETTING1 0x000000C0
+#define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT 0
+#define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_MASK 0x0000FFFF
+#define DSI_VID_VCA_SETTING1_BURST_LP BIT(16)
+
+#define DSI_VID_VCA_SETTING2 0x000000C4
+#define DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT 0
+#define DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_MASK 0x0000FFFF
+#define DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_SHIFT 16
+#define DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_MASK 0xFFFF0000
+
+#define DSI_CMD_MODE_STS_CTL 0x000000F4
+#define DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN BIT(0)
+#define DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN BIT(1)
+#define DSI_CMD_MODE_STS_CTL_ERR_SDI1_UNDERRUN_EN BIT(2)
+#define DSI_CMD_MODE_STS_CTL_ERR_SDI2_UNDERRUN_EN BIT(3)
+#define DSI_CMD_MODE_STS_CTL_ERR_UNWANTED_RD_EN BIT(4)
+#define DSI_CMD_MODE_STS_CTL_CSM_RUNNING_EN BIT(5)
+#define DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EDGE BIT(16)
+#define DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EDGE BIT(17)
+#define DSI_CMD_MODE_STS_CTL_ERR_SDI1_UNDERRUN_EDGE BIT(18)
+#define DSI_CMD_MODE_STS_CTL_ERR_SDI2_UNDERRUN_EDGE BIT(19)
+#define DSI_CMD_MODE_STS_CTL_ERR_UNWANTED_RD_EDGE BIT(20)
+#define DSI_CMD_MODE_STS_CTL_CSM_RUNNING_EDGE BIT(21)
+
+#define DSI_DIRECT_CMD_STS_CTL 0x000000F8
+#define DSI_DIRECT_CMD_STS_CTL_CMD_TRANSMISSION_EN BIT(0)
+#define DSI_DIRECT_CMD_STS_CTL_WRITE_COMPLETED_EN BIT(1)
+#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_COMPLETED_EN BIT(2)
+#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_EN BIT(3)
+#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_RECEIVED_EN BIT(4)
+#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN BIT(5)
+#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_RECEIVED_EN BIT(6)
+#define DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN BIT(7)
+#define DSI_DIRECT_CMD_STS_CTL_BTA_COMPLETED_EN BIT(8)
+#define DSI_DIRECT_CMD_STS_CTL_BTA_FINISHED_EN BIT(9)
+#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_WITH_ERR_EN BIT(10)
+#define DSI_DIRECT_CMD_STS_CTL_CMD_TRANSMISSION_EDGE BIT(16)
+#define DSI_DIRECT_CMD_STS_CTL_WRITE_COMPLETED_EDGE BIT(17)
+#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_COMPLETED_EDGE BIT(18)
+#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_EDGE BIT(19)
+#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_RECEIVED_EDGE BIT(20)
+#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EDGE BIT(21)
+#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_RECEIVED_EDGE BIT(22)
+#define DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EDGE BIT(23)
+#define DSI_DIRECT_CMD_STS_CTL_BTA_COMPLETED_EDGE BIT(24)
+#define DSI_DIRECT_CMD_STS_CTL_BTA_FINISHED_EDGE BIT(25)
+#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_WITH_ERR_EDGE BIT(26)
+
+#define DSI_VID_MODE_STS_CTL 0x00000100
+#define DSI_VID_MODE_STS_CTL_VSG_RUNNING BIT(0)
+#define DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA BIT(1)
+#define DSI_VID_MODE_STS_CTL_ERR_MISSING_HSYNC BIT(2)
+#define DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC BIT(3)
+#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_LENGTH BIT(4)
+#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_HEIGHT BIT(5)
+#define DSI_VID_MODE_STS_CTL_ERR_BURSTWRITE BIT(6)
+#define DSI_VID_MODE_STS_CTL_ERR_LONGWRITE BIT(7)
+#define DSI_VID_MODE_STS_CTL_ERR_LONGREAD BIT(8)
+#define DSI_VID_MODE_STS_CTL_ERR_VRS_WRONG_LENGTH BIT(9)
+#define DSI_VID_MODE_STS_CTL_VSG_RUNNING_EDGE BIT(16)
+#define DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA_EDGE BIT(17)
+#define DSI_VID_MODE_STS_CTL_ERR_MISSING_HSYNC_EDGE BIT(18)
+#define DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC_EDGE BIT(19)
+#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_LENGTH_EDGE BIT(20)
+#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_HEIGHT_EDGE BIT(21)
+#define DSI_VID_MODE_STS_CTL_ERR_BURSTWRITE_EDGE BIT(22)
+#define DSI_VID_MODE_STS_CTL_ERR_LONGWRITE_EDGE BIT(23)
+#define DSI_VID_MODE_STS_CTL_ERR_LONGREAD_EDGE BIT(24)
+#define DSI_VID_MODE_STS_CTL_ERR_VRS_WRONG_LENGTH_EDGE BIT(25)
+#define DSI_VID_MODE_STS_CTL_VSG_RECOVERY_EDGE BIT(26)
+
+#define DSI_TG_STS_CTL 0x00000104
+#define DSI_MCTL_DHPY_ERR_CTL 0x00000108
+#define DSI_MCTL_MAIN_STS_CLR 0x00000110
+
+#define DSI_CMD_MODE_STS_CLR 0x00000114
+#define DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR BIT(0)
+#define DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR BIT(1)
+#define DSI_CMD_MODE_STS_CLR_ERR_SDI1_UNDERRUN_CLR BIT(2)
+#define DSI_CMD_MODE_STS_CLR_ERR_SDI2_UNDERRUN_CLR BIT(3)
+#define DSI_CMD_MODE_STS_CLR_ERR_UNWANTED_RD_CLR BIT(4)
+#define DSI_CMD_MODE_STS_CLR_CSM_RUNNING_CLR BIT(5)
+
+#define DSI_DIRECT_CMD_STS_CLR 0x00000118
+#define DSI_DIRECT_CMD_STS_CLR_CMD_TRANSMISSION_CLR BIT(0)
+#define DSI_DIRECT_CMD_STS_CLR_WRITE_COMPLETED_CLR BIT(1)
+#define DSI_DIRECT_CMD_STS_CLR_TRIGGER_COMPLETED_CLR BIT(2)
+#define DSI_DIRECT_CMD_STS_CLR_READ_COMPLETED_CLR BIT(3)
+#define DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_RECEIVED_CLR BIT(4)
+#define DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR BIT(5)
+#define DSI_DIRECT_CMD_STS_CLR_TRIGGER_RECEIVED_CLR BIT(6)
+#define DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR BIT(7)
+#define DSI_DIRECT_CMD_STS_CLR_BTA_COMPLETED_CLR BIT(8)
+#define DSI_DIRECT_CMD_STS_CLR_BTA_FINISHED_CLR BIT(9)
+#define DSI_DIRECT_CMD_STS_CLR_READ_COMPLETED_WITH_ERR_CLR BIT(10)
+
+#define DSI_DIRECT_CMD_RD_STS_CLR 0x0000011C
+#define DSI_VID_MODE_STS_CLR 0x00000120
+#define DSI_TG_STS_CLR 0x00000124
+#define DSI_MCTL_DPHY_ERR_CLR 0x00000128
+#define DSI_MCTL_MAIN_STS_FLAG 0x00000130
+#define DSI_CMD_MODE_STS_FLAG 0x00000134
+#define DSI_DIRECT_CMD_STS_FLAG 0x00000138
+#define DSI_DIRECT_CMD_RD_STS_FLAG 0x0000013C
+#define DSI_VID_MODE_STS_FLAG 0x00000140
+#define DSI_TG_STS_FLAG 0x00000144
+
+#define DSI_DPHY_LANES_TRIM 0x00000150
+#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT1_SHIFT 0
+#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT1_MASK 0x00000003
+#define DSI_DPHY_LANES_TRIM_DPHY_CD_OFF_DAT1 BIT(2)
+#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_DAT1 BIT(3)
+#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_DAT1 BIT(4)
+#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_DAT1 BIT(5)
+#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_CLK_SHIFT 6
+#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_CLK_MASK 0x000000C0
+#define DSI_DPHY_LANES_TRIM_DPHY_LP_RX_VIL_CLK_SHIFT 8
+#define DSI_DPHY_LANES_TRIM_DPHY_LP_RX_VIL_CLK_MASK 0x00000300
+#define DSI_DPHY_LANES_TRIM_DPHY_LP_TX_SLEWRATE_CLK_SHIFT 10
+#define DSI_DPHY_LANES_TRIM_DPHY_LP_TX_SLEWRATE_CLK_MASK 0x00000C00
+#define DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_81 0
+#define DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90 BIT(12)
+#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_CLK BIT(13)
+#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_CLK BIT(14)
+#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_CLK BIT(15)
+#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT2 BIT(16)
+#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_DAT2 BIT(18)
+#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_DAT2 BIT(19)
+#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_DAT2 BIT(20)
+
+#define DSI_ID_REG	0x00000FF0
+
+#endif /* __DRM_MCDE_DSI_REGS */
-- 
2.20.1



More information about the dri-devel mailing list