diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index cb88528e..bcf2a21 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -315,6 +315,8 @@ source "drivers/gpu/drm/tve200/Kconfig"
 
 source "drivers/gpu/drm/xen/Kconfig"
 
+source "drivers/gpu/drm/tidss/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index a6771ce..4373490 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -107,3 +107,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_TVE200) += tve200/
 obj-$(CONFIG_DRM_XEN) += xen/
+obj-$(CONFIG_DRM_TIDSS) += tidss/
diff --git a/drivers/gpu/drm/tidss/Kconfig b/drivers/gpu/drm/tidss/Kconfig
new file mode 100644
index 0000000..8a8245b
--- /dev/null
+++ b/drivers/gpu/drm/tidss/Kconfig
@@ -0,0 +1,14 @@
+config DRM_TIDSS
+	tristate "DRM Support for TI Keystone"
+	depends on DRM && OF
+	depends on ARM || ARM64 || COMPILE_TEST
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	select VIDEOMODE_HELPERS
+	help
+	  The TI Keystone family SoCs introduced a new generation of
+	  Display SubSystem. They are called DSS6 and DSS7 and at the
+	  time of writing this these DSSs are found on 66AK2Gx and
+	  AM6x SoCs. Set this to Y or M to add display support for
+	  TI Keystone family platforms.
diff --git a/drivers/gpu/drm/tidss/Makefile b/drivers/gpu/drm/tidss/Makefile
new file mode 100644
index 0000000..6b8b4d4
--- /dev/null
+++ b/drivers/gpu/drm/tidss/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+tidss-y := tidss_crtc.o \
+	tidss_dispc6.o \
+	tidss_drv.o \
+	tidss_encoder.o \
+	tidss_irq.o \
+	tidss_kms.o \
+	tidss_plane.o
+
+obj-$(CONFIG_DRM_TIDSS) += tidss.o
diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
new file mode 100644
index 0000000..dcc164d
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_dispc.h"
+#include "tidss_drv.h"
+#include "tidss_irq.h"
+
+/* -----------------------------------------------------------------------------
+ * Page Flip
+ */
+
+static void tidss_crtc_finish_page_flip(struct tidss_crtc *tcrtc)
+{
+	struct drm_device *ddev = tcrtc->crtc.dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+	bool busy;
+
+	/*
+	 * New settings are taken into use at VFP, and GO bit is cleared at
+	 * the same time. This happens before the vertical blank interrupt.
+	 * So there is a small change that the driver sets GO bit after VFP, but
+	 * before vblank, and we have to check for that case here.
+	 */
+	busy = tidss->dispc_ops->vp_go_busy(tidss->dispc, tcrtc->hw_videoport);
+	if (busy)
+		return;
+
+	spin_lock_irqsave(&ddev->event_lock, flags);
+
+	event = tcrtc->event;
+	tcrtc->event = NULL;
+
+	if (!event) {
+		spin_unlock_irqrestore(&ddev->event_lock, flags);
+		return;
+	}
+
+	drm_crtc_send_vblank_event(&tcrtc->crtc, event);
+
+	spin_unlock_irqrestore(&ddev->event_lock, flags);
+
+	drm_crtc_vblank_put(&tcrtc->crtc);
+}
+
+void tidss_crtc_vblank_irq(struct drm_crtc *crtc)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+	drm_crtc_handle_vblank(crtc);
+
+	tidss_crtc_finish_page_flip(tcrtc);
+}
+
+void tidss_crtc_framedone_irq(struct drm_crtc *crtc)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+	complete(&tcrtc->framedone_completion);
+}
+
+void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+	dev_err_ratelimited(crtc->dev->dev, "CRTC%u SYNC LOST: (irq %llx)\n",
+			    tcrtc->hw_videoport, irqstatus);
+}
+
+/* -----------------------------------------------------------------------------
+ * CRTC Functions
+ */
+
+static int tidss_crtc_atomic_check(struct drm_crtc *crtc,
+				   struct drm_crtc_state *state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	int r;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	if (!state->enable)
+		return 0;
+
+	r = tidss->dispc_ops->vp_check(tidss->dispc, tcrtc->hw_videoport,
+				       state);
+
+	return r;
+}
+
+static void tidss_crtc_atomic_flush(struct drm_crtc *crtc,
+				    struct drm_crtc_state *old_crtc_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s, crtc enabled %d, event %p\n",
+		__func__, tcrtc->enabled, crtc->state->event);
+
+	/* Only flush the CRTC if it is currently enabled. */
+	if (!tcrtc->enabled)
+		return;
+
+	/* If the GO bit is stuck we better quit here. */
+	if (WARN_ON(tidss->dispc_ops->vp_go_busy(tidss->dispc,
+						 tcrtc->hw_videoport)))
+		return;
+
+	/* We should have event if CRTC is enabled through out this commit. */
+	WARN_ON(!crtc->state->event);
+
+	tidss->dispc_ops->vp_setup(tidss->dispc,
+				   tcrtc->hw_videoport,
+				   crtc->state);
+
+	WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+	spin_lock_irq(&ddev->event_lock);
+	tidss->dispc_ops->vp_go(tidss->dispc, tcrtc->hw_videoport);
+
+	if (crtc->state->event) {
+		tcrtc->event = crtc->state->event;
+		crtc->state->event = NULL;
+	}
+
+	spin_unlock_irq(&ddev->event_lock);
+}
+
+static void tidss_crtc_atomic_enable(struct drm_crtc *crtc,
+				     struct drm_crtc_state *old_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	int r;
+
+	dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	r = tidss->dispc_ops->vp_set_clk_rate(tidss->dispc, tcrtc->hw_videoport,
+					      mode->clock * 1000);
+	if (r != 0)
+		return;
+
+	r = tidss->dispc_ops->vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+	if (r != 0)
+		return;
+
+	tidss->dispc_ops->vp_setup(tidss->dispc, tcrtc->hw_videoport,
+				   crtc->state);
+
+	/* Turn vertical blanking interrupt reporting on. */
+	drm_crtc_vblank_on(crtc);
+
+	if (tidss->dispc_ops->vp_prepare)
+		tidss->dispc_ops->vp_prepare(tidss->dispc, tcrtc->hw_videoport,
+					     crtc->state);
+
+	tcrtc->enabled = true;
+
+	tidss->dispc_ops->vp_enable(tidss->dispc, tcrtc->hw_videoport,
+				    crtc->state);
+
+	spin_lock_irq(&ddev->event_lock);
+
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+
+	spin_unlock_irq(&ddev->event_lock);
+}
+
+static void tidss_crtc_atomic_disable(struct drm_crtc *crtc,
+				      struct drm_crtc_state *old_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event);
+
+	reinit_completion(&tcrtc->framedone_completion);
+
+	tidss->dispc_ops->vp_disable(tidss->dispc, tcrtc->hw_videoport);
+
+	if (!wait_for_completion_timeout(&tcrtc->framedone_completion,
+					 msecs_to_jiffies(500)))
+		dev_err(tidss->dev, "Timeout waiting for framedone on crtc %d",
+			tcrtc->hw_videoport);
+
+	if (tidss->dispc_ops->vp_unprepare)
+		tidss->dispc_ops->vp_unprepare(tidss->dispc,
+					       tcrtc->hw_videoport);
+
+	spin_lock_irq(&ddev->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&ddev->event_lock);
+
+	tcrtc->enabled = false;
+
+	drm_crtc_vblank_off(crtc);
+
+	tidss->dispc_ops->vp_disable_clk(tidss->dispc, tcrtc->hw_videoport);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static
+enum drm_mode_status tidss_crtc_mode_valid(struct drm_crtc *crtc,
+					   const struct drm_display_mode *mode)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	return tidss->dispc_ops->vp_mode_valid(tidss->dispc,
+					       tcrtc->hw_videoport,
+					       mode);
+}
+
+static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+	.atomic_check = tidss_crtc_atomic_check,
+	.atomic_flush = tidss_crtc_atomic_flush,
+	.atomic_enable = tidss_crtc_atomic_enable,
+	.atomic_disable = tidss_crtc_atomic_disable,
+
+	.mode_valid = tidss_crtc_mode_valid,
+};
+
+static void tidss_crtc_reset(struct drm_crtc *crtc)
+{
+	struct tidss_crtc_state *tcrtc;
+
+	if (crtc->state)
+		__drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+	kfree(crtc->state);
+
+	tcrtc = kzalloc(sizeof(*tcrtc), GFP_KERNEL);
+	if (!tcrtc) {
+		crtc->state = NULL;
+		return;
+	}
+
+	crtc->state = &tcrtc->base;
+	crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *
+tidss_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct tidss_crtc_state *state, *current_state;
+
+	if (WARN_ON(!crtc->state))
+		return NULL;
+
+	current_state = to_tidss_crtc_state(crtc->state);
+
+	state = kmalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+	state->bus_format = current_state->bus_format;
+	state->bus_flags = current_state->bus_flags;
+
+	return &state->base;
+}
+
+
+static int tidss_crtc_atomic_set_property(struct drm_crtc *crtc,
+					  struct drm_crtc_state *state,
+					  struct drm_property *property,
+					  uint64_t val)
+{
+	return -EINVAL;
+}
+
+static int tidss_crtc_atomic_get_property(struct drm_crtc *crtc,
+					  const struct drm_crtc_state *state,
+					  struct drm_property *property,
+					  uint64_t *val)
+{
+	return -EINVAL;
+}
+
+static int tidss_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	tidss_irq_enable_vblank(crtc);
+
+	return 0;
+}
+
+static void tidss_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss_irq_disable_vblank(crtc);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static const struct drm_crtc_funcs crtc_funcs = {
+	.reset = tidss_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.atomic_duplicate_state = tidss_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.atomic_set_property = tidss_crtc_atomic_set_property,
+	.atomic_get_property = tidss_crtc_atomic_get_property,
+	.enable_vblank = tidss_crtc_enable_vblank,
+	.disable_vblank = tidss_crtc_disable_vblank,
+};
+
+static void tidss_crtc_install_properties(struct tidss_device *tidss,
+					  const struct tidss_vp_feat *vp_feat,
+					  struct drm_crtc *crtc)
+{
+}
+
+struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, u32 hw_videoport,
+				     struct drm_plane *primary)
+{
+	struct tidss_crtc *tcrtc;
+	struct drm_crtc *crtc;
+	const struct tidss_vp_feat *vp_feat;
+	unsigned int gamma_lut_size = 0;
+	int ret;
+
+	vp_feat = tidss->dispc_ops->vp_feat(tidss->dispc, hw_videoport);
+
+	tcrtc = devm_kzalloc(tidss->dev, sizeof(*tcrtc), GFP_KERNEL);
+	if (!tcrtc)
+		return ERR_PTR(-ENOMEM);
+
+	tcrtc->hw_videoport = hw_videoport;
+	init_completion(&tcrtc->framedone_completion);
+
+	crtc =  &tcrtc->crtc;
+
+	ret = drm_crtc_init_with_planes(tidss->ddev, crtc, primary,
+					NULL, &crtc_funcs, NULL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+	/*
+	 * The dispc API adapts to what ever size we ask from it no
+	 * matter what HW supports. X-server assumes 256 element gamma
+	 * tables so lets use that. Size of HW gamma table size is
+	 * found from struct tidss_vp_feat that is extracted with
+	 * dispc_vp_feats(). If gamma_size is 0 gamma table is not
+	 * supported.
+	 */
+	if (vp_feat->color.gamma_size)
+		gamma_lut_size = 256;
+
+	drm_crtc_enable_color_mgmt(crtc, 0, vp_feat->color.has_ctm,
+				   gamma_lut_size);
+	if (gamma_lut_size)
+		drm_mode_crtc_set_gamma_size(crtc, gamma_lut_size);
+
+	tidss_crtc_install_properties(tidss, vp_feat, crtc);
+
+	return tcrtc;
+}
diff --git a/drivers/gpu/drm/tidss/tidss_crtc.h b/drivers/gpu/drm/tidss/tidss_crtc.h
new file mode 100644
index 0000000..827e983
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_crtc.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_CRTC_H__
+#define __TIDSS_CRTC_H__
+
+#include <drm/drm_crtc.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+
+#include "tidss_dispc.h"
+
+#define to_tidss_crtc(c) container_of((c), struct tidss_crtc, crtc)
+
+struct tidss_crtc {
+	struct drm_crtc crtc;
+
+	u32 hw_videoport;
+
+	struct drm_pending_vblank_event *event;
+
+	/* has crtc_atomic_enable been called? */
+	bool enabled;
+
+	struct completion framedone_completion;
+};
+
+#define to_tidss_crtc_state(x) container_of(x, struct tidss_crtc_state, base)
+
+struct tidss_crtc_state {
+	/* Must be first. */
+	struct drm_crtc_state base;
+
+	u32 bus_format;
+	u32 bus_flags;
+};
+
+struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, u32 hw_videoport,
+				     struct drm_plane *primary);
+
+
+void tidss_crtc_vblank_irq(struct drm_crtc *crtc);
+void tidss_crtc_framedone_irq(struct drm_crtc *crtc);
+void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus);
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
new file mode 100644
index 0000000..18c87d5
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC_H__
+#define __TIDSS_DISPC_H__
+
+struct dispc_device;
+struct tidss_device;
+
+struct drm_crtc_state;
+
+#define DSS_MAX_CHANNELS 8
+#define DSS_MAX_PLANES 8
+
+/*
+ * Based on the above 2 defines the bellow defines describe following
+ * u64 IRQ bits:
+ *
+ * bit group |dev |mrg0|mrg1|mrg2|mrg3|mrg4|mrg5|mrg6|mrg7|plane 0-7|<unused> |
+ * bit use   |Dfou|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|UUUU|UUUU| | | | | |
+ * bit number|0-3 |4-7 |8-11|            12-35            |  36-43  |  44-63  |
+ *
+ * device bits:	D = OCP error
+ * WB bits:	f = frame done wb, o = wb buffer overflow,
+ *		u = wb buffer uncomplete
+ * vp bits:	F = frame done, E = vsync even, O = vsync odd, L = sync lost
+ * plane bits:	U = fifo underflow
+ */
+
+#define DSS_IRQ_DEVICE_OCP_ERR			BIT_ULL(0)
+
+#define DSS_IRQ_DEVICE_FRAMEDONEWB		BIT_ULL(1)
+#define DSS_IRQ_DEVICE_WBBUFFEROVERFLOW		BIT_ULL(2)
+#define DSS_IRQ_DEVICE_WBUNCOMPLETEERROR	BIT_ULL(3)
+#define DSS_IRQ_DEVICE_WB_MASK			GENMASK_ULL(3, 1)
+
+#define DSS_IRQ_VP_BIT_N(ch, bit)	(4 + 4 * (ch) + (bit))
+#define DSS_IRQ_PLANE_BIT_N(plane, bit) \
+	(DSS_IRQ_VP_BIT_N(DSS_MAX_CHANNELS, 0) + 1 * (plane) + (bit))
+
+#define DSS_IRQ_VP_BIT(ch, bit)	BIT_ULL(DSS_IRQ_VP_BIT_N((ch), (bit)))
+#define DSS_IRQ_PLANE_BIT(plane, bit) \
+	BIT_ULL(DSS_IRQ_PLANE_BIT_N((plane), (bit)))
+
+#define DSS_IRQ_VP_MASK(ch) \
+	GENMASK_ULL(DSS_IRQ_VP_BIT_N((ch), 3), DSS_IRQ_VP_BIT_N((ch), 0))
+#define DSS_IRQ_PLANE_MASK(plane) \
+	GENMASK_ULL(DSS_IRQ_PLANE_BIT_N((plane), 0), \
+		    DSS_IRQ_PLANE_BIT_N((plane), 0))
+
+#define DSS_IRQ_VP_FRAME_DONE(ch)	DSS_IRQ_VP_BIT((ch), 0)
+#define DSS_IRQ_VP_VSYNC_EVEN(ch)	DSS_IRQ_VP_BIT((ch), 1)
+#define DSS_IRQ_VP_VSYNC_ODD(ch)	DSS_IRQ_VP_BIT((ch), 2)
+#define DSS_IRQ_VP_SYNC_LOST(ch)	DSS_IRQ_VP_BIT((ch), 3)
+
+#define DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane)	DSS_IRQ_PLANE_BIT((plane), 0)
+
+struct tidss_vp_feat {
+	struct tidss_vp_color_feat {
+		u32 gamma_size;
+		bool has_ctm;
+	} color;
+};
+
+struct tidss_plane_feat {
+	struct tidss_plane_color_feat {
+		u32 encodings;
+		u32 ranges;
+		enum drm_color_encoding default_encoding;
+		enum drm_color_range default_range;
+	} color;
+	struct tidss_plane_blend_feat {
+		bool global_alpha;
+	} blend;
+};
+
+struct tidss_dispc_ops {
+	u64 (*read_and_clear_irqstatus)(struct dispc_device *dispc);
+	void (*write_irqenable)(struct dispc_device *dispc, u64 enable);
+
+	u32 (*get_memory_bandwidth_limit)(struct dispc_device *dispc);
+
+	int (*get_num_vps)(struct dispc_device *dispc);
+	const char *(*vp_name)(struct dispc_device *dispc,
+			       u32 hw_videoport);
+	const struct tidss_vp_feat *(*vp_feat)(struct dispc_device *dispc,
+					       u32 hw_videoport);
+	void (*vp_prepare)(struct dispc_device *dispc, u32 hw_videoport,
+			   const struct drm_crtc_state *state);
+	void (*vp_enable)(struct dispc_device *dispc, u32 hw_videoport,
+			  const struct drm_crtc_state *state);
+	void (*vp_disable)(struct dispc_device *dispc, u32 hw_videoport);
+	void (*vp_unprepare)(struct dispc_device *dispc, u32 hw_videoport);
+	bool (*vp_go_busy)(struct dispc_device *dispc,
+			   u32 hw_videoport);
+	void (*vp_go)(struct dispc_device *dispc, u32 hw_videoport);
+	enum drm_mode_status (*vp_mode_valid)(struct dispc_device *dispc,
+					u32 hw_videoport,
+					const struct drm_display_mode *mode);
+	int (*vp_check)(struct dispc_device *dispc, u32 hw_videoport,
+			const struct drm_crtc_state *state);
+	void (*vp_setup)(struct dispc_device *dispc, u32 hw_videoport,
+			 const struct drm_crtc_state *state);
+
+	int (*vp_set_clk_rate)(struct dispc_device *dispc,
+			       u32 hw_videoport, unsigned long rate);
+	int (*vp_enable_clk)(struct dispc_device *dispc, u32 hw_videoport);
+	void (*vp_disable_clk)(struct dispc_device *dispc, u32 hw_videoport);
+
+	int (*get_num_planes)(struct dispc_device *dispc);
+	const char *(*plane_name)(struct dispc_device *dispc,
+				  u32 hw_plane);
+	const struct tidss_plane_feat *(*plane_feat)(struct dispc_device *dispc,
+						     u32 hw_plane);
+	int (*plane_enable)(struct dispc_device *dispc, u32 hw_plane,
+			    bool enable);
+	int (*plane_check)(struct dispc_device *dispc, u32 hw_plane,
+			   const struct drm_plane_state *state,
+			   u32 hw_videoport);
+	int (*plane_setup)(struct dispc_device *dispc, u32 hw_plane,
+			   const struct drm_plane_state *state,
+			   u32 hw_videoport);
+
+	int (*runtime_get)(struct dispc_device *dispc);
+	void (*runtime_put)(struct dispc_device *dispc);
+
+	int (*runtime_suspend)(struct dispc_device *dispc);
+	int (*runtime_resume)(struct dispc_device *dispc);
+
+	void (*remove)(struct dispc_device *dispc);
+
+	int (*modeset_init)(struct dispc_device *dispc);
+};
+
+int dispc6_init(struct tidss_device *tidss);
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_dispc6.c b/drivers/gpu/drm/tidss/tidss_dispc6.c
new file mode 100644
index 0000000..7513f3e
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_dispc6.c
@@ -0,0 +1,1507 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_plane.h"
+
+#include "tidss_dispc6.h"
+
+static const struct {
+	u32 fmt;
+	u32 port_width;
+} dispc6_bus_formats[] = {
+	{ MEDIA_BUS_FMT_RGB444_1X12, 12 },
+	{ MEDIA_BUS_FMT_RGB565_1X16, 16 },
+	{ MEDIA_BUS_FMT_RGB666_1X18, 18 },
+	{ MEDIA_BUS_FMT_RGB888_1X24, 24 },
+};
+
+/*
+ * TRM gives bitfields as start:end, where start is the higher bit
+ * number. For example 7:0
+ */
+
+#define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end))
+#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
+#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end))
+#define FLD_MOD(orig, val, start, end) \
+	(((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end))
+
+
+#define REG_GET(dispc, idx, start, end)	\
+	FLD_GET(dispc6_read(dispc, idx), start, end)
+
+#define REG_FLD_MOD(dispc, idx, val, start, end) \
+	dispc6_write(dispc, idx, FLD_MOD(dispc6_read(dispc, idx), val, start, end))
+
+#define VID_REG_GET(dispc, plane, idx, start, end) \
+	FLD_GET(dispc6_vid_read(dispc, plane, idx), start, end)
+
+#define VID_REG_FLD_MOD(dispc, plane, idx, val, start, end) \
+	dispc6_vid_write(dispc, plane, idx, FLD_MOD(dispc6_vid_read(dispc, plane, idx), val, start, end))
+
+
+#define VP_REG_GET(dispc, vp, idx, start, end) \
+	FLD_GET(dispc6_vp_read(dispc, vp, idx), start, end)
+
+#define VP_REG_FLD_MOD(dispc, vp, idx, val, start, end)	\
+	dispc6_vp_write(dispc, vp, idx, FLD_MOD(dispc6_vp_read(dispc, vp, idx), val, start, end))
+
+#define DISPC6_GAMMA_TABLE_SIZE 256
+
+struct dispc_features {
+	/* XXX should these come from the .dts? Min pclk is not feature of DSS IP */
+	unsigned long min_pclk;
+	unsigned long max_pclk;
+};
+
+/* Note: 9MHz is a special allowed case, and is handled separately in the code */
+static const struct dispc_features k2g_dispc_feats = {
+	.min_pclk = 43750000,
+	.max_pclk = 150000000,
+};
+
+static const struct of_device_id dispc6_of_match[] = {
+	{ .compatible = "ti,k2g-dss", .data = &k2g_dispc_feats, },
+	{},
+};
+
+struct dispc_device {
+	struct device *dev;
+
+	void __iomem *base_cfg;
+	void __iomem *base_common;
+	void __iomem *base_vid1;
+	void __iomem *base_ovr1;
+	void __iomem *base_vp1;
+
+	const struct dispc_features *feat;
+
+	struct clk *fclk;
+	struct clk *vp_clk;
+
+	bool is_enabled;
+
+	u32 gamma_table[DISPC6_GAMMA_TABLE_SIZE];
+
+	struct tidss_device *tidss;
+};
+
+static void dispc6_write(struct dispc_device *dispc, u16 reg, u32 val)
+{
+	iowrite32(val, dispc->base_common + reg);
+}
+
+static u32 dispc6_read(struct dispc_device *dispc, u16 reg)
+{
+	return ioread32(dispc->base_common + reg);
+}
+
+static void dispc6_vid_write(struct dispc_device *dispc,
+			     u32 hw_plane, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_vid1;
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc6_vid_read(struct dispc_device *dispc,
+			   u32 hw_plane, u16 reg)
+{
+	void __iomem *base = dispc->base_vid1;
+
+	return ioread32(base + reg);
+}
+
+static void dispc6_ovr_write(struct dispc_device *dispc,
+			     u32 hw_videoport, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_ovr1;
+
+	iowrite32(val, base + reg);
+}
+
+__maybe_unused
+static u32 dispc6_ovr_read(struct dispc_device *dispc,
+			   u32 hw_videoport, u16 reg)
+{
+	void __iomem *base = dispc->base_ovr1;
+
+	return ioread32(base + reg);
+}
+
+static void dispc6_vp_write(struct dispc_device *dispc,
+			    u32 hw_videoport, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_vp1;
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc6_vp_read(struct dispc_device *dispc,
+			  u32 hw_videoport, u16 reg)
+{
+	void __iomem *base = dispc->base_vp1;
+
+	return ioread32(base + reg);
+}
+
+static int dispc6_runtime_get(struct dispc_device *dispc)
+{
+	int r;
+
+	dev_dbg(dispc->dev, "dispc_runtime_get\n");
+
+	r = pm_runtime_get_sync(dispc->dev);
+	WARN_ON(r < 0);
+	return r < 0 ? r : 0;
+}
+
+static void dispc6_runtime_put(struct dispc_device *dispc)
+{
+	int r;
+
+	dev_dbg(dispc->dev, "dispc_runtime_put\n");
+
+	r = pm_runtime_put_sync(dispc->dev);
+	WARN_ON(r < 0);
+}
+
+static u64 dispc6_vp_irq_from_raw(u32 stat)
+{
+	u32 vp = 0;
+	u64 vp_stat = 0;
+
+	if (stat & BIT(0))
+		vp_stat |= DSS_IRQ_VP_FRAME_DONE(vp);
+	if (stat & BIT(1))
+		vp_stat |= DSS_IRQ_VP_VSYNC_EVEN(vp);
+	if (stat & BIT(2))
+		vp_stat |= DSS_IRQ_VP_VSYNC_ODD(vp);
+	if (stat & BIT(4))
+		vp_stat |= DSS_IRQ_VP_SYNC_LOST(vp);
+
+	return vp_stat;
+}
+
+static u32 dispc6_vp_irq_to_raw(u64 vpstat)
+{
+	u32 vp = 0;
+	u32 stat = 0;
+
+	if (vpstat & DSS_IRQ_VP_FRAME_DONE(vp))
+		stat |= BIT(0);
+	if (vpstat & DSS_IRQ_VP_VSYNC_EVEN(vp))
+		stat |= BIT(1);
+	if (vpstat & DSS_IRQ_VP_VSYNC_ODD(vp))
+		stat |= BIT(2);
+	if (vpstat & DSS_IRQ_VP_SYNC_LOST(vp))
+		stat |= BIT(4);
+
+	return stat;
+}
+
+static u64 dispc6_vid_irq_from_raw(u32 stat)
+{
+	u32 plane = 0;
+	u64 vid_stat = 0;
+
+	if (stat & BIT(0))
+		vid_stat |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane);
+
+	return vid_stat;
+}
+
+static u32 dispc6_vid_irq_to_raw(u64 vidstat)
+{
+	u32 plane = 0;
+	u32 stat = 0;
+
+	if (vidstat & DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane))
+		stat |= BIT(0);
+
+	return stat;
+}
+
+
+static u64 dispc6_vp_read_irqstatus(struct dispc_device *dispc,
+				    u32 hw_videoport)
+{
+	u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQSTATUS);
+
+	return dispc6_vp_irq_from_raw(stat);
+}
+
+static void dispc6_vp_write_irqstatus(struct dispc_device *dispc,
+				      u32 hw_videoport,
+				      u64 vpstat)
+{
+	u32 stat = dispc6_vp_irq_to_raw(vpstat);
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQSTATUS, stat);
+}
+
+static u64 dispc6_vid_read_irqstatus(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQSTATUS);
+
+	return dispc6_vid_irq_from_raw(stat);
+}
+
+static void dispc6_vid_write_irqstatus(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       u64 vidstat)
+{
+	u32 stat = dispc6_vid_irq_to_raw(vidstat);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQSTATUS, stat);
+}
+
+
+static u64 dispc6_vp_read_irqenable(struct dispc_device *dispc,
+				    u32 hw_videoport)
+{
+	u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQENABLE);
+
+	return dispc6_vp_irq_from_raw(stat);
+}
+
+static void dispc6_vp_write_irqenable(struct dispc_device *dispc,
+				      u32 hw_videoport,
+				      u64 vpstat)
+{
+	u32 stat = dispc6_vp_irq_to_raw(vpstat);
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQENABLE, stat);
+}
+
+static u64 dispc6_vid_read_irqenable(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQENABLE);
+
+	return dispc6_vid_irq_from_raw(stat);
+}
+
+static void dispc6_vid_write_irqenable(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       u64 vidstat)
+{
+	u32 stat = dispc6_vid_irq_to_raw(vidstat);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQENABLE, stat);
+}
+
+
+static void dispc6_clear_irqstatus(struct dispc_device *dispc, u64 mask)
+{
+	dispc6_vp_write_irqstatus(dispc, 0, mask);
+	dispc6_vid_write_irqstatus(dispc, 0, mask);
+}
+
+static u64 dispc6_read_and_clear_irqstatus(struct dispc_device *dispc)
+{
+	u64 stat = 0;
+
+	/* always clear the top level irqstatus */
+	dispc6_write(dispc, DISPC_IRQSTATUS,
+		     dispc6_read(dispc, DISPC_IRQSTATUS));
+
+	stat |= dispc6_vp_read_irqstatus(dispc, 0);
+	stat |= dispc6_vid_read_irqstatus(dispc, 0);
+
+	dispc6_clear_irqstatus(dispc, stat);
+
+	return stat;
+}
+
+static u64 dispc6_read_irqenable(struct dispc_device *dispc)
+{
+	u64 stat = 0;
+
+	stat |= dispc6_vp_read_irqenable(dispc, 0);
+	stat |= dispc6_vid_read_irqenable(dispc, 0);
+
+	return stat;
+}
+
+static void dispc6_write_irqenable(struct dispc_device *dispc, u64 mask)
+{
+	u64 old_mask = dispc6_read_irqenable(dispc);
+
+	/* clear the irqstatus for newly enabled irqs */
+	dispc6_clear_irqstatus(dispc, (mask ^ old_mask) & mask);
+
+	dispc6_vp_write_irqenable(dispc, 0, mask);
+	dispc6_vid_write_irqenable(dispc, 0, mask);
+
+	dispc6_write(dispc, DISPC_IRQENABLE_SET, (1 << 0) | (1 << 7));
+
+	/* flush posted write */
+	dispc6_read_irqenable(dispc);
+}
+
+static void dispc6_set_num_datalines(struct dispc_device *dispc,
+				     u32 hw_videoport, int num_lines)
+{
+	int v;
+
+	switch (num_lines) {
+	case 12:
+		v = 0; break;
+	case 16:
+		v = 1; break;
+	case 18:
+		v = 2; break;
+	case 24:
+		v = 3; break;
+	case 30:
+		v = 4; break;
+	case 36:
+		v = 5; break;
+	default:
+		WARN_ON(1);
+		v = 3;
+		break;
+	}
+
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, v, 10, 8);
+}
+
+static void dispc6_vp_enable(struct dispc_device *dispc, u32 hw_videoport,
+			     const struct drm_crtc_state *state)
+{
+	const struct drm_display_mode *mode = &state->adjusted_mode;
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	bool align, onoff, rf, ieo, ipc, ihs, ivs;
+	unsigned int i;
+	u32 port_width;
+	u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) {
+		if (dispc6_bus_formats[i].fmt != tstate->bus_format)
+			continue;
+
+		port_width = dispc6_bus_formats[i].port_width;
+		break;
+	}
+
+	if (WARN_ON(i == ARRAY_SIZE(dispc6_bus_formats)))
+		return;
+
+	dispc6_set_num_datalines(dispc, hw_videoport, port_width);
+
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_H,
+			FLD_VAL(hsw - 1, 7, 0) |
+			FLD_VAL(hfp - 1, 19, 8) |
+			FLD_VAL(hbp - 1, 31, 20));
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_V,
+			FLD_VAL(vsw - 1, 7, 0) |
+			FLD_VAL(vfp, 19, 8) |
+			FLD_VAL(vbp, 31, 20));
+
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		ivs = true;
+	else
+		ivs = false;
+
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		ihs = true;
+	else
+		ihs = false;
+
+	if (tstate->bus_flags & DRM_BUS_FLAG_DE_LOW)
+		ieo = true;
+	else
+		ieo = false;
+
+	if (tstate->bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+		ipc = true;
+	else
+		ipc = false;
+
+	/* always use the 'rf' setting */
+	onoff = true;
+
+	if (tstate->bus_flags & DRM_BUS_FLAG_SYNC_NEGEDGE)
+		rf = false;
+	else
+		rf = true;
+
+	/* always use aligned syncs */
+	align = true;
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_POL_FREQ,
+			FLD_VAL(align, 18, 18) |
+			FLD_VAL(onoff, 17, 17) |
+			FLD_VAL(rf, 16, 16) |
+			FLD_VAL(ieo, 15, 15) |
+			FLD_VAL(ipc, 14, 14) |
+			FLD_VAL(ihs, 13, 13) |
+			FLD_VAL(ivs, 12, 12));
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN,
+			FLD_VAL(mode->hdisplay - 1, 11, 0) |
+			FLD_VAL(mode->vdisplay - 1, 27, 16));
+
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 0, 0);
+}
+
+static void dispc6_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
+{
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0, 0, 0);
+}
+
+static bool dispc6_vp_go_busy(struct dispc_device *dispc,
+			      u32 hw_videoport)
+{
+	return VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL, 5, 5);
+}
+
+static void dispc6_vp_go(struct dispc_device *dispc,
+			 u32 hw_videoport)
+{
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 5, 5);
+}
+
+static u16 c8_to_c12(u8 c8)
+{
+	u16 c12;
+
+	c12 = c8 << 4;
+
+	/* Replication logic: Copy c8 4 MSB to 4 LSB for full scale c12 */
+	c12 = c8 >> 4;
+
+	return c12;
+}
+
+static u64 argb8888_to_argb12121212(u32 argb8888)
+{
+	u8 a, r, g, b;
+	u64 v;
+
+	a = (argb8888 >> 24) & 0xff;
+	r = (argb8888 >> 16) & 0xff;
+	g = (argb8888 >> 8) & 0xff;
+	b = (argb8888 >> 0) & 0xff;
+
+	v = ((u64)c8_to_c12(a) << 36) | ((u64)c8_to_c12(r) << 24) |
+	    ((u64)c8_to_c12(g) << 12) | (u64)c8_to_c12(b);
+
+	return v;
+}
+
+static void dispc6_vp_set_default_color(struct dispc_device *dispc,
+					u32 hw_videoport, u32 default_color)
+{
+	u64 v;
+
+	v = argb8888_to_argb12121212(default_color);
+
+	dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR, v & 0xffffffff);
+	dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR2,
+			 (v >> 32) & 0xffff);
+}
+
+static enum drm_mode_status dispc6_vp_mode_valid(struct dispc_device *dispc,
+						 u32 hw_videoport,
+						 const struct drm_display_mode *mode)
+{
+	u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+	/* special case for 9MHz */
+	if (mode->clock * 1000 < dispc->feat->min_pclk && mode->clock != 9000)
+		return MODE_CLOCK_LOW;
+
+	if (mode->clock * 1000 > dispc->feat->max_pclk)
+		return MODE_CLOCK_HIGH;
+
+	if (mode->hdisplay > 4096)
+		return MODE_BAD;
+
+	if (mode->vdisplay > 4096)
+		return MODE_BAD;
+
+	/* TODO: add interlace support */
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return MODE_NO_INTERLACE;
+
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	if (hsw < 1 || hsw > 256 ||
+	    hfp < 1 || hfp > 4096 ||
+	    hbp < 1 || hbp > 4096)
+		return MODE_BAD_HVALUE;
+
+	if (vsw < 1 || vsw > 256 ||
+	    vfp < 0 || vfp > 4095 ||
+	    vbp < 0 || vbp > 4095)
+		return MODE_BAD_VVALUE;
+
+	return MODE_OK;
+}
+
+static int dispc6_vp_check(struct dispc_device *dispc, u32 hw_videoport,
+			   const struct drm_crtc_state *state)
+{
+	const struct drm_display_mode *mode = &state->adjusted_mode;
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	enum drm_mode_status ok;
+	unsigned int i;
+
+	ok = dispc6_vp_mode_valid(dispc, hw_videoport, mode);
+	if (ok != MODE_OK) {
+		dev_dbg(dispc->dev, "%s: bad mode: %ux%u pclk %u kHz\n",
+			__func__, mode->hdisplay, mode->vdisplay, mode->clock);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) {
+		if (dispc6_bus_formats[i].fmt == tstate->bus_format)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(dispc6_bus_formats)) {
+		dev_dbg(dispc->dev, "%s: Unsupported bus format: %u\n",
+			__func__, tstate->bus_format);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dispc6_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport)
+{
+	int ret = clk_prepare_enable(dispc->vp_clk);
+
+	if (ret)
+		dev_err(dispc->dev, "%s: enabling clk failed: %d\n", __func__,
+			ret);
+
+	return ret;
+}
+
+static void dispc6_vp_disable_clk(struct dispc_device *dispc,
+				  u32 hw_videoport)
+{
+	clk_disable_unprepare(dispc->vp_clk);
+}
+
+static int dispc6_vp_set_clk_rate(struct dispc_device *dispc,
+				  u32 hw_videoport, unsigned long rate)
+{
+	int r;
+	unsigned long new_rate;
+
+	r = clk_set_rate(dispc->vp_clk, rate);
+	if (r) {
+		dev_err(dispc->dev, "Failed to set vp clk rate to %lu\n",
+			rate);
+		return r;
+	}
+
+	new_rate = clk_get_rate(dispc->vp_clk);
+
+	if (rate != new_rate)
+		dev_warn(dispc->dev,
+			 "Failed to get exact pix clock %lu != %lu\n",
+			 rate, new_rate);
+
+	dev_dbg(dispc->dev, "New VP rate %lu Hz (requested %lu Hz)\n",
+		clk_get_rate(dispc->vp_clk), rate);
+
+	return 0;
+}
+
+/* CSC */
+
+struct color_conv_coef {
+	int ry, rcb, rcr;
+	int gy, gcb, gcr;
+	int by, bcb, bcr;
+	int roffset, goffset, boffset;
+	bool full_range;
+};
+
+static void dispc6_vid_write_color_conv_coefs(struct dispc_device *dispc,
+					      u32 hw_plane,
+					      const struct color_conv_coef *ct)
+{
+#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0))
+
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(0), CVAL(ct->rcr, ct->ry));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(1), CVAL(ct->gy,  ct->rcb));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(2), CVAL(ct->gcb, ct->gcr));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(3), CVAL(ct->bcr, ct->by));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(4), CVAL(0, ct->bcb));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(5),
+			 FLD_VAL(ct->roffset, 15, 3) |
+			 FLD_VAL(ct->goffset, 31, 19));
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(6),
+			 FLD_VAL(ct->boffset, 15, 3));
+
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+			ct->full_range, 11, 11);
+
+#undef CVAL
+}
+
+static void dispc6_vid_csc_setup(struct dispc_device *dispc)
+{
+	/* YUV -> RGB, ITU-R BT.601, full range */
+	const struct color_conv_coef coefs_yuv2rgb_bt601_full = {
+		256,   0,  358,
+		256, -88, -182,
+		256, 452,    0,
+		0, -2048, -2048,
+		true,
+	};
+
+	dispc6_vid_write_color_conv_coefs(dispc, 0, &coefs_yuv2rgb_bt601_full);
+}
+
+static void dispc6_vid_csc_enable(struct dispc_device *dispc,
+				  u32 hw_plane, bool enable)
+{
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 9, 9);
+}
+
+/* SCALER */
+
+static u32 dispc6_calc_fir_inc(u32 in, u32 out)
+{
+	return (u32)div_u64(0x200000ull * in, out);
+}
+
+struct dispc6_vid_fir_coefs {
+	s16 c2[16];
+	s16 c1[16];
+	u16 c0[9];
+};
+
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_null = {
+	.c2 = {	0 },
+	.c1 = { 0 },
+	.c0 = { 512, 512, 512, 512, 512, 512, 512, 512, 256,  },
+};
+
+/* M=8, Upscale x >= 1 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m8 = {
+	.c2 = {	0, -4, -8, -16, -24, -32, -40, -48, 0, 2, 4, 6, 8, 6, 4, 2,  },
+	.c1 = { 0, 28, 56, 94, 132, 176, 220, 266, -56, -60, -64, -62, -60, -50, -40, -20,  },
+	.c0 = { 512, 506, 500, 478, 456, 424, 392, 352, 312,  },
+};
+
+/* 5-tap, M=22, Downscale Ratio 2.5 < x < 3 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_5tap = {
+	.c2 = { 16, 20, 24, 30, 36, 42, 48, 56, 0, 0, 0, 2, 4, 8, 12, 14,  },
+	.c1 = { 132, 140, 148, 156, 164, 172, 180, 186, 64, 72, 80, 88, 96, 104, 112, 122,  },
+	.c0 = { 216, 216, 216, 214, 212, 208, 204, 198, 192,  },
+};
+
+/* 3-tap, M=22, Downscale Ratio 2.5 < x < 3 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_3tap = {
+	.c1 = { 100, 118, 136, 156, 176, 196, 216, 236, 0, 10, 20, 30, 40, 54, 68, 84,  },
+	.c0 = { 312, 310, 308, 302, 296, 286, 276, 266, 256,  },
+};
+
+enum dispc6_vid_fir_coef_set {
+	DISPC6_VID_FIR_COEF_HORIZ,
+	DISPC6_VID_FIR_COEF_HORIZ_UV,
+	DISPC6_VID_FIR_COEF_VERT,
+	DISPC6_VID_FIR_COEF_VERT_UV,
+};
+
+static void dispc6_vid_write_fir_coefs(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       enum dispc6_vid_fir_coef_set coef_set,
+				       const struct dispc6_vid_fir_coefs *coefs)
+{
+	static const u16 c0_regs[] = {
+		[DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H0,
+		[DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H0_C,
+		[DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V0,
+		[DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V0_C,
+	};
+
+	static const u16 c12_regs[] = {
+		[DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H12,
+		[DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H12_C,
+		[DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V12,
+		[DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V12_C,
+	};
+
+	const u16 c0_base = c0_regs[coef_set];
+	const u16 c12_base = c12_regs[coef_set];
+	int phase;
+
+	for (phase = 0; phase <= 8; ++phase) {
+		u16 reg = c0_base + phase * 4;
+		u16 c0 = coefs->c0[phase];
+
+		dispc6_vid_write(dispc, hw_plane, reg, c0);
+	}
+
+	for (phase = 0; phase <= 15; ++phase) {
+		u16 reg = c12_base + phase * 4;
+		s16 c1, c2;
+		u32 c12;
+
+		c1 = coefs->c1[phase];
+		c2 = coefs->c2[phase];
+		c12 = FLD_VAL(c1, 19, 10) | FLD_VAL(c2, 29, 20);
+
+		dispc6_vid_write(dispc, hw_plane, reg, c12);
+	}
+}
+
+static void dispc6_vid_write_scale_coefs(struct dispc_device *dispc,
+					 u32 hw_plane)
+{
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ,
+				   &dispc6_fir_coefs_null);
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ_UV,
+				   &dispc6_fir_coefs_null);
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT,
+				   &dispc6_fir_coefs_null);
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT_UV,
+				   &dispc6_fir_coefs_null);
+}
+
+static void dispc6_vid_set_scaling(struct dispc_device *dispc,
+				   u32 hw_plane,
+				   u32 orig_width, u32 orig_height,
+				   u32 out_width, u32 out_height,
+				   u32 fourcc)
+{
+	u32 in_w, in_h, in_w_uv, in_h_uv;
+	u32 fir_hinc, fir_vinc, fir_hinc_uv, fir_vinc_uv;
+	bool scale_x, scale_y;
+	bool five_taps = false;		/* XXX always 3-tap for now */
+
+	in_w = in_w_uv = orig_width;
+	in_h = in_h_uv = orig_height;
+
+	switch (fourcc) {
+	case DRM_FORMAT_NV12:
+		/* UV is subsampled by 2 horizontally and vertically */
+		in_h_uv >>= 1;
+		in_w_uv >>= 1;
+		break;
+
+	case DRM_FORMAT_YUYV:
+	case DRM_FORMAT_UYVY:
+		/* UV is subsampled by 2 horizontally */
+		in_w_uv >>= 1;
+		break;
+
+	default:
+		break;
+	}
+
+	scale_x = in_w != out_width || in_w_uv != out_width;
+	scale_y = in_h != out_height || in_h_uv != out_height;
+
+	/* HORIZONTAL RESIZE ENABLE */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_x, 7, 7);
+
+	/* VERTICAL RESIZE ENABLE */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_y, 8, 8);
+
+	/* Skip the rest if no scaling is used */
+	if (!scale_x && !scale_y)
+		return;
+
+	/* VERTICAL 5-TAPS  */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, five_taps, 21, 21);
+
+	/* FIR INC */
+
+	fir_hinc = dispc6_calc_fir_inc(in_w, out_width);
+	fir_vinc = dispc6_calc_fir_inc(in_h, out_height);
+	fir_hinc_uv = dispc6_calc_fir_inc(in_w_uv, out_width);
+	fir_vinc_uv = dispc6_calc_fir_inc(in_h_uv, out_height);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH, fir_hinc);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV, fir_vinc);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH2, fir_hinc_uv);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV2, fir_vinc_uv);
+
+	dispc6_vid_write_scale_coefs(dispc, hw_plane);
+}
+
+/* OTHER */
+
+static const struct {
+	u32 fourcc;
+	u8 dss_code;
+} dispc6_color_formats[] = {
+	{ DRM_FORMAT_ARGB4444, 0x0, },
+	{ DRM_FORMAT_ABGR4444, 0x1, },
+	{ DRM_FORMAT_RGBA4444, 0x2, },
+
+	{ DRM_FORMAT_RGB565, 0x3, },
+	{ DRM_FORMAT_BGR565, 0x4, },
+
+	{ DRM_FORMAT_ARGB1555, 0x5, },
+	{ DRM_FORMAT_ABGR1555, 0x6, },
+
+	{ DRM_FORMAT_ARGB8888, 0x7, },
+	{ DRM_FORMAT_ABGR8888, 0x8, },
+	{ DRM_FORMAT_RGBA8888, 0x9, },
+	{ DRM_FORMAT_BGRA8888, 0xa, },
+
+	{ DRM_FORMAT_XRGB8888, 0x27, },
+	{ DRM_FORMAT_XBGR8888, 0x28, },
+	{ DRM_FORMAT_RGBX8888, 0x29, },
+	{ DRM_FORMAT_BGRX8888, 0x2a, },
+
+	{ DRM_FORMAT_YUYV, 0x3e, },
+	{ DRM_FORMAT_UYVY, 0x3f, },
+
+	{ DRM_FORMAT_NV12, 0x3d, },
+};
+
+static bool dispc6_fourcc_is_yuv(u32 fourcc)
+{
+	switch (fourcc) {
+	case DRM_FORMAT_YUYV:
+	case DRM_FORMAT_UYVY:
+	case DRM_FORMAT_NV12:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void dispc6_plane_set_pixel_format(struct dispc_device *dispc,
+					  u32 hw_plane, u32 fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) {
+		if (dispc6_color_formats[i].fourcc == fourcc) {
+			VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+					dispc6_color_formats[i].dss_code,
+					6, 1);
+			return;
+		}
+	}
+
+	WARN_ON(1);
+}
+
+static s32 pixinc(int pixels, u8 ps)
+{
+	if (pixels == 1)
+		return 1;
+	else if (pixels > 1)
+		return 1 + (pixels - 1) * ps;
+	else if (pixels < 0)
+		return 1 - (-pixels + 1) * ps;
+
+	WARN_ON(1);
+	return 0;
+}
+
+static
+const struct tidss_plane_feat *dispc6_plane_feat(struct dispc_device *dispc,
+						 u32 hw_plane)
+{
+	static const struct tidss_plane_feat pfeat = {
+		.color = {
+			.encodings = BIT(DRM_COLOR_YCBCR_BT601),
+			.ranges = BIT(DRM_COLOR_YCBCR_FULL_RANGE),
+			.default_encoding = DRM_COLOR_YCBCR_BT601,
+			.default_range = DRM_COLOR_YCBCR_FULL_RANGE,
+		},
+		.blend = {
+			.global_alpha = false,
+		},
+	};
+
+	return &pfeat;
+}
+
+static int dispc6_plane_check(struct dispc_device *dispc, u32 hw_plane,
+			      const struct drm_plane_state *state,
+			      u32 hw_videoport)
+{
+	return 0; /* XXX: Dummy check function to be implemented later */
+}
+
+static int dispc6_plane_setup(struct dispc_device *dispc, u32 hw_plane,
+			      const struct drm_plane_state *state,
+			      u32 hw_videoport)
+{
+	u32 fourcc = state->fb->format->format;
+	dma_addr_t paddr = dispc7_plane_state_paddr(state);
+	u16 cpp = state->fb->format->cpp[0];
+	u32 fb_width = state->fb->pitches[0] / cpp;
+	u32 src_w = state->src_w >> 16;
+	u32 src_h = state->src_h >> 16;
+
+	dispc6_plane_set_pixel_format(dispc, hw_plane, fourcc);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_0, paddr);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_1, paddr);
+
+	if (state->fb->format->num_planes == 2) {
+		dma_addr_t p_uv_addr = dispc7_plane_state_p_uv_addr(state);
+
+		dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_0, p_uv_addr);
+		dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_1, p_uv_addr);
+	}
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_PICTURE_SIZE,
+			 (src_w - 1) | ((src_h - 1) << 16));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_PIXEL_INC,
+			 pixinc(1, cpp));
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_ROW_INC,
+			 pixinc(1 + fb_width - src_w, cpp));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_POSITION,
+			 state->crtc_x | (state->crtc_y << 16));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_SIZE,
+			 (state->crtc_w - 1) | ((state->crtc_h - 1) << 16));
+
+	dispc6_vid_set_scaling(dispc, hw_plane, src_w, src_h,
+			       state->crtc_w, state->crtc_h,
+			       fourcc);
+
+	/* enable YUV->RGB color conversion */
+	if (dispc6_fourcc_is_yuv(fourcc))
+		dispc6_vid_csc_enable(dispc, hw_plane, true);
+	else
+		dispc6_vid_csc_enable(dispc, hw_plane, false);
+
+	/* hw_videoport */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, 0, 16, 14);
+
+	return 0;
+}
+
+static int dispc6_plane_enable(struct dispc_device *dispc,
+			       u32 hw_plane, bool enable)
+{
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 0, 0);
+	return 0;
+}
+
+static u32 dispc6_vid_get_fifo_size(struct dispc_device *dispc,
+				    u32 hw_plane)
+{
+	const u32 unit_size = 16;	/* 128-bits */
+
+	return VID_REG_GET(dispc, hw_plane, DISPC_VID_BUF_SIZE_STATUS, 15, 0) *
+	       unit_size;
+}
+
+static void dispc6_vid_set_mflag_threshold(struct dispc_device *dispc,
+					   u32 hw_plane,
+					   u32 low, u32 high)
+{
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_MFLAG_THRESHOLD,
+			 FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0));
+}
+
+static void dispc6_mflag_setup(struct dispc_device *dispc)
+{
+	u32 hw_plane = 0;
+	const u32 unit_size = 16;	/* 128-bits */
+	u32 size = dispc6_vid_get_fifo_size(dispc, hw_plane);
+	u32 low, high;
+
+	/* MFLAG_CTRL */
+	REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 1, 1, 0);
+	/* MFLAG_START */
+	REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 0, 2, 2);
+
+	/*
+	 * Simulation team suggests below thesholds:
+	 * HT = fifosize * 5 / 8;
+	 * LT = fifosize * 4 / 8;
+	 */
+
+	low = size * 4 / 8 / unit_size;
+	high = size * 5 / 8 / unit_size;
+
+	dispc6_vid_set_mflag_threshold(dispc, hw_plane, low, high);
+}
+
+static void dispc6_initial_config(struct dispc_device *dispc)
+{
+	dispc6_vid_csc_setup(dispc);
+	dispc6_mflag_setup(dispc);
+
+	/* Enable the gamma Shadow bit-field */
+	VP_REG_FLD_MOD(dispc, 0, DISPC_VP_CONFIG, 1, 2, 2);
+}
+
+static int dispc6_init_features(struct dispc_device *dispc)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(dispc6_of_match, dispc->dev->of_node);
+	if (!match) {
+		dev_err(dispc->dev, "Unsupported DISPC version\n");
+		return -ENODEV;
+	}
+
+	dispc->feat = match->data;
+
+	return 0;
+}
+
+static int dispc6_get_num_planes(struct dispc_device *dispc)
+{
+	return 1;
+}
+
+static int dispc6_get_num_vps(struct dispc_device *dispc)
+{
+	return 1;
+}
+
+static const struct tidss_vp_feat *dispc6_vp_feat(struct dispc_device *dispc,
+						  u32 hw_videoport)
+{
+	static const struct tidss_vp_feat vp_feat = {
+		.color = {
+			.gamma_size = DISPC6_GAMMA_TABLE_SIZE,
+			.has_ctm = false, /* Driver implementation missing */
+		},
+	};
+
+	return &vp_feat;
+}
+
+static void dispc6_vp_write_gamma_table(struct dispc_device *dispc,
+					u32 hw_videoport)
+{
+	u32 *table = dispc->gamma_table;
+	unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table);
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s: hw_videoport %d\n", __func__, hw_videoport);
+
+	for (i = 0; i < hwlen; ++i) {
+		u32 v = table[i];
+
+		v |= i << 24;
+
+		dispc6_vp_write(dispc, hw_videoport, DISPC_VP_GAMMA_TABLE, v);
+	}
+}
+
+static void dispc6_restore_gamma_tables(struct dispc_device *dispc)
+{
+	dev_dbg(dispc->dev, "%s()\n", __func__);
+
+	dispc6_vp_write_gamma_table(dispc, 0);
+}
+
+static const struct drm_color_lut dispc6_vp_gamma_default_lut[] = {
+	{ .red = 0, .green = 0, .blue = 0, },
+	{ .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, },
+};
+
+static void dispc6_vp_set_gamma(struct dispc_device *dispc,
+				u32 hw_videoport,
+				const struct drm_color_lut *lut,
+				unsigned int length)
+{
+	u32 *table = dispc->gamma_table;
+	unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table);
+	static const unsigned int hwbits = 8;
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s: hw_videoport %d, lut len %u, hw len %u\n",
+		__func__, hw_videoport, length, hwlen);
+
+	if (lut == NULL || length < 2) {
+		lut = dispc6_vp_gamma_default_lut;
+		length = ARRAY_SIZE(dispc6_vp_gamma_default_lut);
+	}
+
+	for (i = 0; i < length - 1; ++i) {
+		unsigned int first = i * (hwlen - 1) / (length - 1);
+		unsigned int last = (i + 1) * (hwlen - 1) / (length - 1);
+		unsigned int w = last - first;
+		u16 r, g, b;
+		unsigned int j;
+
+		if (w == 0)
+			continue;
+
+		for (j = 0; j <= w; j++) {
+			r = (lut[i].red * (w - j) + lut[i + 1].red * j) / w;
+			g = (lut[i].green * (w - j) + lut[i + 1].green * j) / w;
+			b = (lut[i].blue * (w - j) + lut[i + 1].blue * j) / w;
+
+			r >>= 16 - hwbits;
+			g >>= 16 - hwbits;
+			b >>= 16 - hwbits;
+
+			table[first + j] = (r << (hwbits * 2)) |
+					   (g << hwbits) | b;
+		}
+	}
+
+	if (dispc->is_enabled)
+		dispc6_vp_write_gamma_table(dispc, hw_videoport);
+}
+
+static void dispc6_vp_set_color_mgmt(struct dispc_device *dispc,
+				     u32 hw_videoport,
+				     const struct drm_crtc_state *state)
+{
+	struct drm_color_lut *lut = NULL;
+	unsigned int length = 0;
+
+	if (!state->color_mgmt_changed)
+		return;
+
+	if (state->gamma_lut) {
+		lut = (struct drm_color_lut *) state->gamma_lut->data;
+		length = state->gamma_lut->length / sizeof(*lut);
+	}
+
+	dispc6_vp_set_gamma(dispc, hw_videoport, lut, length);
+}
+
+static void dispc6_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
+			    const struct drm_crtc_state *state)
+{
+	dispc6_vp_set_default_color(dispc, hw_videoport, 0);
+	dispc6_vp_set_color_mgmt(dispc, hw_videoport, state);
+}
+
+static int dispc6_init_gamma_tables(struct dispc_device *dispc)
+{
+	dispc6_vp_set_gamma(dispc, 0, NULL, 0);
+
+	return 0;
+}
+
+static u32 dispc6_get_memory_bandwidth_limit(struct dispc_device *dispc)
+{
+	u32 limit = 0;
+
+	/* Optional maximum memory bandwidth */
+	of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth",
+			     &limit);
+
+	return limit;
+}
+
+static const char *dispc6_plane_name(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	return "vid1";
+}
+
+static const char *dispc6_vp_name(struct dispc_device *dispc,
+				  u32 hw_videoport)
+{
+	return "vp1";
+}
+
+static int dispc6_runtime_suspend(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "suspend\n");
+
+	dispc->is_enabled = false;
+
+	clk_disable_unprepare(dispc->fclk);
+
+	return 0;
+}
+
+static int dispc6_runtime_resume(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "resume\n");
+
+	clk_prepare_enable(dispc->fclk);
+
+	if (REG_GET(dispc, DISPC_SYSSTATUS, 0, 0) == 0)
+		dev_warn(dev, "DISPC FUNC RESET not done!\n");
+	if (REG_GET(dispc, DISPC_SYSSTATUS, 1, 1) == 0)
+		dev_warn(dev, "DISPC VP RESET not done!\n");
+
+	dispc6_initial_config(dispc);
+
+	dispc6_restore_gamma_tables(dispc);
+
+	dispc->is_enabled = true;
+
+	return 0;
+}
+
+static int dispc6_modeset_init(struct dispc_device *dispc)
+{
+	struct tidss_device *tidss = dispc->tidss;
+	struct device *dev = tidss->dev;
+	const u32 hw_videoport = 0;
+	const u32 crtc_mask = 1;
+	const u32 hw_plane_id = 0;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+	u32 enc_type;
+	int ret;
+	struct tidss_plane *tplane;
+	struct tidss_crtc *tcrtc;
+	struct drm_encoder *enc;
+	u32 fourccs[ARRAY_SIZE(dispc6_color_formats)];
+	unsigned int i;
+
+	/* first find if there is a connected panel/bridge */
+
+	ret = drm_of_find_panel_or_bridge(dev->of_node, hw_videoport, 0, &panel, &bridge);
+	if (ret) {
+		dev_dbg(dev, "no panel or bridge found\n");
+		return ret;
+	}
+
+	if (panel) {
+		dev_dbg(dev, "Setting up panel\n");
+
+		enc_type = DRM_MODE_ENCODER_DPI;
+
+		bridge = devm_drm_panel_bridge_add(dev, panel, DRM_MODE_CONNECTOR_DPI);
+		if (IS_ERR(bridge)) {
+			dev_err(dev, "failed to set up panel bridge\n");
+			return PTR_ERR(bridge);
+		}
+	} else {
+		enc_type = DRM_MODE_ENCODER_NONE;
+	}
+
+	/* then create a plane, a crtc and an encoder for the panel/bridge */
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i)
+		fourccs[i] = dispc6_color_formats[i].fourcc;
+
+	tplane = tidss_plane_create(tidss, hw_plane_id, DRM_PLANE_TYPE_PRIMARY,
+				    crtc_mask, fourccs, ARRAY_SIZE(fourccs));
+	if (IS_ERR(tplane)) {
+		dev_err(tidss->dev, "plane create failed\n");
+		return PTR_ERR(tplane);
+	}
+
+	tidss->planes[tidss->num_planes++] = &tplane->plane;
+
+	tcrtc = tidss_crtc_create(tidss, hw_videoport, &tplane->plane);
+	if (IS_ERR(tcrtc)) {
+		dev_err(tidss->dev, "crtc create failed\n");
+		return PTR_ERR(tcrtc);
+	}
+
+	tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc;
+
+	enc = tidss_encoder_create(tidss, enc_type, 1 << tcrtc->crtc.index);
+	if (IS_ERR(enc)) {
+		dev_err(tidss->dev, "encoder create failed\n");
+		return PTR_ERR(enc);
+	}
+
+	ret = drm_bridge_attach(enc, bridge, NULL);
+	if (ret) {
+		dev_err(tidss->dev, "bridge attach failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dispc6_remove(struct dispc_device *dispc);
+
+static const struct tidss_dispc_ops dispc6_ops = {
+	.read_and_clear_irqstatus = dispc6_read_and_clear_irqstatus,
+	.write_irqenable = dispc6_write_irqenable,
+
+	.get_memory_bandwidth_limit = dispc6_get_memory_bandwidth_limit,
+
+	.get_num_vps = dispc6_get_num_vps,
+	.vp_name = dispc6_vp_name,
+	.vp_feat = dispc6_vp_feat,
+	.vp_enable = dispc6_vp_enable,
+	.vp_disable = dispc6_vp_disable,
+	.vp_go_busy = dispc6_vp_go_busy,
+	.vp_go = dispc6_vp_go,
+	.vp_mode_valid = dispc6_vp_mode_valid,
+	.vp_check = dispc6_vp_check,
+	.vp_setup = dispc6_vp_setup,
+
+	.vp_set_clk_rate = dispc6_vp_set_clk_rate,
+	.vp_enable_clk = dispc6_vp_enable_clk,
+	.vp_disable_clk = dispc6_vp_disable_clk,
+
+	.get_num_planes = dispc6_get_num_planes,
+	.plane_name = dispc6_plane_name,
+	.plane_feat = dispc6_plane_feat,
+	.plane_enable = dispc6_plane_enable,
+	.plane_check = dispc6_plane_check,
+	.plane_setup = dispc6_plane_setup,
+
+	.runtime_get = dispc6_runtime_get,
+	.runtime_put = dispc6_runtime_put,
+
+	.runtime_suspend = dispc6_runtime_suspend,
+	.runtime_resume = dispc6_runtime_resume,
+
+	.remove = dispc6_remove,
+
+	.modeset_init = dispc6_modeset_init,
+};
+
+static int dispc6_iomap_resource(struct platform_device *pdev, const char *name,
+				 void __iomem **base)
+{
+	struct resource *res;
+	void __iomem *b;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+	b = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(b)) {
+		dev_err(&pdev->dev, "cannot ioremap resource '%s'\n", name);
+		return PTR_ERR(b);
+	}
+
+	*base = b;
+
+	return 0;
+}
+
+int dispc6_init(struct tidss_device *tidss)
+{
+	struct device *dev = tidss->dev;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dispc_device *dispc;
+	int r;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dispc = devm_kzalloc(dev, sizeof(*dispc), GFP_KERNEL);
+	if (!dispc)
+		return -ENOMEM;
+
+	dispc->tidss = tidss;
+	dispc->dev = dev;
+
+	r = dispc6_init_features(dispc);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "cfg", &dispc->base_cfg);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "common", &dispc->base_common);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "vid1", &dispc->base_vid1);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "ovr1", &dispc->base_ovr1);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "vp1", &dispc->base_vp1);
+	if (r)
+		goto err_free;
+
+	dev_dbg(dev, "dispc6_bind: iores ok\n");
+
+	dispc->fclk = devm_clk_get(dev, "fck");
+	if (IS_ERR(dispc->fclk)) {
+		dev_err(dev, "Failed to get fclk\n");
+		r = PTR_ERR(dispc->fclk);
+		goto err_free;
+	}
+
+	dispc->vp_clk = devm_clk_get(dev, "vp1");
+	if (IS_ERR(dispc->vp_clk)) {
+		dev_err(dev, "Failed to get vp1 clk\n");
+		r = PTR_ERR(dispc->vp_clk);
+		goto err_free;
+	}
+
+	r = dispc6_init_gamma_tables(dispc);
+	if (r)
+		goto err_free;
+
+	tidss->dispc_ops = &dispc6_ops;
+	tidss->dispc = dispc;
+
+	dev_dbg(dev, "%s done\n", __func__);
+
+	return 0;
+err_free:
+	dev_err(dev, "%s failed: %d\n", __func__, r);
+	return r;
+}
+
+static void dispc6_remove(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "dispc6_unbind\n");
+
+	dispc->tidss->dispc_ops = NULL;
+	dispc->tidss->dispc = NULL;
+
+	dev_dbg(dev, "dispc6_unbind done\n");
+}
diff --git a/drivers/gpu/drm/tidss/tidss_dispc6.h b/drivers/gpu/drm/tidss/tidss_dispc6.h
new file mode 100644
index 0000000..80197c8
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_dispc6.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC6_H__
+#define __TIDSS_DISPC6_H__
+
+/* COMMON */
+
+#define DISPC_REVISION			0x000
+#define DISPC_SYSCONFIG			0x004
+#define DISPC_SYSSTATUS			0x008
+
+#define DISPC_IRQ_EOI			0x020
+#define DISPC_IRQSTATUS_RAW		0x024
+#define DISPC_IRQSTATUS			0x028
+#define DISPC_IRQENABLE_SET		0x02c
+#define DISPC_IRQENABLE_CLR		0x030
+#define DISPC_IRQWAKEEN			0x034
+
+#define DISPC_GLOBAL_MFLAG_ATTRIBUTE	0x040
+#define DISPC_GLOBAL_BUFFER		0x044
+#define DISPC_BA0_FLIPIMMEDIATE_EN	0x048
+
+#define DISPC_DBG_CONTROL		0x04c
+#define DISPC_DBG_STATUS		0x050
+
+#define DISPC_CLKGATING_DISABLE		0x054
+
+/* VID */
+
+#define DISPC_VID_ACCUH_0		0x0
+#define DISPC_VID_ACCUH_1		0x4
+#define DISPC_VID_ACCUH2_0		0x8
+#define DISPC_VID_ACCUH2_1		0xc
+
+#define DISPC_VID_ACCUV_0		0x10
+#define DISPC_VID_ACCUV_1		0x14
+#define DISPC_VID_ACCUV2_0		0x18
+#define DISPC_VID_ACCUV2_1		0x1c
+
+#define DISPC_VID_ATTRIBUTES		0x20
+#define DISPC_VID_ATTRIBUTES2		0x24
+
+#define DISPC_VID_BA_0			0x28
+#define DISPC_VID_BA_1			0x2c
+#define DISPC_VID_BA_UV_0		0x30
+#define DISPC_VID_BA_UV_1		0x34
+#define DISPC_VID_BUF_SIZE_STATUS	0x38
+#define DISPC_VID_BUF_THRESHOLD		0x3c
+
+#define DISPC_VID_CONV_COEF(n)		(0x40 + (n) * 4)
+
+#define DISPC_VID_FIRH			0x5c
+#define DISPC_VID_FIRH2			0x60
+#define DISPC_VID_FIRV			0x64
+#define DISPC_VID_FIRV2			0x68
+
+#define DISPC_VID_FIR_COEFS_H0		0x6c
+#define DISPC_VID_FIR_COEF_H0(phase)	(0x6c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H0_C	0x90
+#define DISPC_VID_FIR_COEF_H0_C(phase)	(0x90 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_H12		0xb4
+#define DISPC_VID_FIR_COEF_H12(phase)	(0xb4 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H12_C	0xf4
+#define DISPC_VID_FIR_COEF_H12_C(phase)	(0xf4 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V0		0x134
+#define DISPC_VID_FIR_COEF_V0(phase)	(0x134 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V0_C	0x158
+#define DISPC_VID_FIR_COEF_V0_C(phase)	(0x158 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V12		0x17c
+#define DISPC_VID_FIR_COEF_V12(phase)	(0x17c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V12_C	0x1bc
+#define DISPC_VID_FIR_COEF_V12_C(phase)	(0x1bc + (phase) * 4)
+
+#define DISPC_VID_IRQENABLE		0x200
+#define DISPC_VID_IRQSTATUS		0x204
+
+#define DISPC_VID_MFLAG_THRESHOLD	0x208
+#define DISPC_VID_PICTURE_SIZE		0x20c
+#define DISPC_VID_PIXEL_INC		0x210
+#define DISPC_VID_POSITION		0x214
+#define DISPC_VID_PRELOAD		0x218
+#define DISPC_VID_ROW_INC		0x21c
+#define DISPC_VID_SIZE			0x220
+
+/* OVR */
+
+#define DISPC_OVR_DEFAULT_COLOR		0x08
+#define DISPC_OVR_DEFAULT_COLOR2	0x0c
+
+/* VP */
+
+#define DISPC_VP_CONFIG			0x00
+#define DISPC_VP_CONTROL		0x04
+#define DISPC_VP_GAMMA_TABLE		0x20
+#define DISPC_VP_IRQENABLE		0x3c
+#define DISPC_VP_IRQSTATUS		0x40
+#define DISPC_VP_POL_FREQ		0x4c
+#define DISPC_VP_SIZE_SCREEN		0x50
+#define DISPC_VP_TIMING_H		0x54
+#define DISPC_VP_TIMING_V		0x58
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
new file mode 100644
index 0000000..8485f9a
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_drv.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/console.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "tidss_dispc.h"
+#include "tidss_drv.h"
+#include "tidss_irq.h"
+#include "tidss_kms.h"
+
+/* -----------------------------------------------------------------------------
+ * Device Information
+ */
+
+DEFINE_DRM_GEM_CMA_FOPS(tidss_fops);
+
+static struct drm_driver tidss_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
+				| DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
+	.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,
+	.dumb_create		= drm_gem_cma_dumb_create,
+	.fops			= &tidss_fops,
+	.name			= "tidss",
+	.desc			= "TI Keystone DSS",
+	.date			= "20180215",
+	.major			= 1,
+	.minor			= 0,
+
+	.irq_preinstall		= tidss_irq_preinstall,
+	.irq_postinstall	= tidss_irq_postinstall,
+	.irq_handler		= tidss_irq_handler,
+	.irq_uninstall		= tidss_irq_uninstall,
+};
+
+#ifdef CONFIG_PM
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int tidss_pm_runtime_suspend(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return tidss->dispc_ops->runtime_suspend(tidss->dispc);
+}
+
+static int tidss_pm_runtime_resume(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+	int r;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	r = tidss->dispc_ops->runtime_resume(tidss->dispc);
+	if (r)
+		return r;
+
+	tidss_irq_resume(tidss->ddev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tidss_suspend(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return drm_mode_config_helper_suspend(tidss->ddev);
+}
+
+static int tidss_resume(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return drm_mode_config_helper_resume(tidss->ddev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops tidss_pm_ops = {
+	.runtime_suspend = tidss_pm_runtime_suspend,
+	.runtime_resume = tidss_pm_runtime_resume,
+	SET_SYSTEM_SLEEP_PM_OPS(tidss_suspend, tidss_resume)
+};
+
+#endif /* CONFIG_PM */
+
+/* -----------------------------------------------------------------------------
+ * Platform driver
+ */
+
+static int tidss_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tidss_device *tidss;
+	struct drm_device *ddev;
+	int ret;
+	int irq;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	tidss = devm_kzalloc(dev, sizeof(*tidss), GFP_KERNEL);
+	if (tidss == NULL)
+		return -ENOMEM;
+
+	tidss->dev = dev;
+	tidss->features = of_device_get_match_data(dev);
+
+	platform_set_drvdata(pdev, tidss);
+
+	ddev = drm_dev_alloc(&tidss_driver, dev);
+	if (IS_ERR(ddev))
+		return PTR_ERR(ddev);
+
+	tidss->ddev = ddev;
+	ddev->dev_private = tidss;
+
+	pm_runtime_enable(dev);
+
+	ret = tidss->features->dispc_init(tidss);
+	if (ret) {
+		dev_err(dev, "failed to initialize dispc: %d\n", ret);
+		goto err_disable_pm;
+	}
+
+#ifndef CONFIG_PM_SLEEP
+	/* no PM, so force enable DISPC */
+	tidss->dispc_ops->runtime_resume(tidss->dispc);
+#endif
+
+	ret = tidss_modeset_init(tidss);
+	if (ret < 0) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to init DRM/KMS (%d)\n", ret);
+		goto err_runtime_suspend;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = irq;
+		dev_err(dev, "platform_get_irq failed: %d\n", ret);
+		goto err_modeset_cleanup;
+	}
+
+	ret = drm_irq_install(ddev, irq);
+	if (ret) {
+		dev_err(dev, "drm_irq_install failed: %d\n", ret);
+		goto err_modeset_cleanup;
+	}
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+	if (ddev->mode_config.num_connector) {
+		struct drm_fbdev_cma *fbdev;
+
+		fbdev = drm_fbdev_cma_init(ddev, 32,
+					   ddev->mode_config.num_connector);
+		if (IS_ERR(fbdev)) {
+			dev_err(tidss->dev, "fbdev init failed\n");
+			ret =  PTR_ERR(fbdev);
+			goto err_irq_uninstall;
+		}
+
+		tidss->fbdev = fbdev;
+	}
+#endif
+
+	drm_kms_helper_poll_init(ddev);
+
+	ret = drm_dev_register(ddev, 0);
+	if (ret) {
+		dev_err(dev, "failed to register DRM device\n");
+		goto err_poll_fini;
+	}
+
+	dev_dbg(dev, "%s done\n", __func__);
+
+	return 0;
+
+err_poll_fini:
+	drm_kms_helper_poll_fini(ddev);
+
+	if (tidss->fbdev)
+		drm_fbdev_cma_fini(tidss->fbdev);
+
+	drm_atomic_helper_shutdown(ddev);
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+err_irq_uninstall:
+#endif
+	drm_irq_uninstall(ddev);
+
+err_modeset_cleanup:
+	drm_mode_config_cleanup(ddev);
+
+err_runtime_suspend:
+#ifndef CONFIG_PM_SLEEP
+	/* no PM, so force disable DISPC */
+	tidss->dispc_ops->runtime_suspend(tidss->dispc);
+#endif
+
+	tidss->dispc_ops->remove(tidss->dispc);
+
+err_disable_pm:
+	pm_runtime_disable(dev);
+
+	drm_dev_put(ddev);
+
+	return ret;
+}
+
+static int tidss_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tidss_device *tidss = platform_get_drvdata(pdev);
+	struct drm_device *ddev = tidss->ddev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	drm_dev_unregister(ddev);
+
+	drm_kms_helper_poll_fini(ddev);
+
+	if (tidss->fbdev)
+		drm_fbdev_cma_fini(tidss->fbdev);
+
+	drm_atomic_helper_shutdown(ddev);
+
+	drm_irq_uninstall(ddev);
+
+	drm_mode_config_cleanup(ddev);
+
+#ifndef CONFIG_PM_SLEEP
+	/* no PM, so force disable DISPC */
+	tidss->dispc_ops->runtime_suspend(tidss->dispc);
+#endif
+
+	tidss->dispc_ops->remove(tidss->dispc);
+
+	pm_runtime_disable(dev);
+
+	drm_dev_put(ddev);
+
+	dev_dbg(dev, "%s done\n", __func__);
+
+	return 0;
+}
+
+static const struct tidss_features tidss_k2g_features = {
+	.dispc_init = dispc6_init,
+};
+
+static const struct of_device_id tidss_of_table[] = {
+	{ .compatible = "ti,k2g-dss", .data = &tidss_k2g_features },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, tidss_of_table);
+
+static struct platform_driver tidss_platform_driver = {
+	.probe		= tidss_probe,
+	.remove		= tidss_remove,
+	.driver		= {
+		.name	= "tidss",
+#ifdef CONFIG_PM
+		.pm	= &tidss_pm_ops,
+#endif
+		.of_match_table = tidss_of_table,
+		.suppress_bind_attrs = true,
+	},
+};
+
+module_platform_driver(tidss_platform_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("TI Keystone DSS Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h
new file mode 100644
index 0000000..ff055aa
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_drv.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DRV_H__
+#define __TIDSS_DRV_H__
+
+#include <linux/spinlock.h>
+
+struct tidss_device {
+	struct device *dev;		/* Underlying DSS device */
+	struct drm_device *ddev;	/* DRM device for DSS */
+
+	struct drm_fbdev_cma *fbdev;
+
+	struct dispc_device *dispc;
+	const struct tidss_dispc_ops *dispc_ops;
+
+	const struct tidss_features *features;
+
+	unsigned int num_crtcs;
+	struct drm_crtc *crtcs[8];
+
+	unsigned int num_planes;
+	struct drm_plane *planes[8];
+
+	spinlock_t wait_lock;	/* protects the irq masks */
+	u64 irq_mask;		/* enabled irqs in addition to wait_list */
+	u64 irq_uf_mask;	/* underflow irq bits for all planes */
+
+	struct drm_atomic_state *saved_state;
+};
+
+struct tidss_features {
+	int (*dispc_init)(struct tidss_device *tidss);
+};
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c
new file mode 100644
index 0000000..32ee294
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/export.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_of.h>
+
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_crtc.h"
+
+/* -----------------------------------------------------------------------------
+ * Encoder
+ */
+
+static int tidss_encoder_atomic_check(struct drm_encoder *encoder,
+				      struct drm_crtc_state *crtc_state,
+				      struct drm_connector_state *conn_state)
+{
+	struct drm_device *ddev = encoder->dev;
+	struct tidss_crtc_state *tcrtc_state = to_tidss_crtc_state(crtc_state);
+	struct drm_display_info *di = &conn_state->connector->display_info;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	// XXX any cleaner way to set bus format and flags?
+	tcrtc_state->bus_format = di->bus_formats[0];
+	tcrtc_state->bus_flags = di->bus_flags;
+
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+	.atomic_check = tidss_encoder_atomic_check,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss,
+					 u32 encoder_type, u32 possible_crtcs)
+{
+	struct drm_encoder *enc;
+	int ret;
+
+	enc = devm_kzalloc(tidss->dev, sizeof(*enc), GFP_KERNEL);
+	if (!enc)
+		return ERR_PTR(-ENOMEM);
+
+	enc->possible_crtcs = possible_crtcs;
+
+	ret = drm_encoder_init(tidss->ddev, enc, &encoder_funcs,
+			       encoder_type, NULL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	drm_encoder_helper_add(enc, &encoder_helper_funcs);
+
+	dev_dbg(tidss->dev, "Encoder create done\n");
+
+	return enc;
+}
diff --git a/drivers/gpu/drm/tidss/tidss_encoder.h b/drivers/gpu/drm/tidss/tidss_encoder.h
new file mode 100644
index 0000000..06854d6
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_encoder.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_ENCODER_H__
+#define __TIDSS_ENCODER_H__
+
+#include <drm/drm_encoder.h>
+
+struct tidss_device;
+
+struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss,
+					 u32 encoder_type, u32 possible_crtcs);
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_irq.c b/drivers/gpu/drm/tidss/tidss_irq.c
new file mode 100644
index 0000000..07c4e1f
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_irq.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+
+#include "tidss_irq.h"
+#include "tidss_drv.h"
+#include "tidss_dispc.h"
+#include "tidss_crtc.h"
+#include "tidss_plane.h"
+
+/* call with wait_lock and dispc runtime held */
+static void tidss_irq_update(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+
+	assert_spin_locked(&tidss->wait_lock);
+
+	tidss->dispc_ops->write_irqenable(tidss->dispc, tidss->irq_mask);
+}
+
+void tidss_irq_enable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	u32 hw_videoport = tcrtc->hw_videoport;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+	tidss->irq_mask |= DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+			   DSS_IRQ_VP_VSYNC_ODD(hw_videoport);
+	tidss_irq_update(ddev);
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
+
+void tidss_irq_disable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	u32 hw_videoport = tcrtc->hw_videoport;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+	tidss->irq_mask &= ~(DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+			     DSS_IRQ_VP_VSYNC_ODD(hw_videoport));
+	tidss_irq_update(ddev);
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
+
+static void tidss_irq_fifo_underflow(struct tidss_device *tidss,
+				     u64 irqstatus)
+{
+	static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL,
+				      DEFAULT_RATELIMIT_BURST);
+	unsigned int i;
+	u64 masked;
+
+	spin_lock(&tidss->wait_lock);
+	masked = irqstatus & tidss->irq_uf_mask & tidss->irq_mask;
+	spin_unlock(&tidss->wait_lock);
+
+	if (!masked)
+		return;
+
+	if (!__ratelimit(&_rs))
+		return;
+
+	DRM_ERROR("FIFO underflow on ");
+
+	for (i = 0; i < DSS_MAX_PLANES; ++i) {
+		if (masked & DSS_IRQ_PLANE_FIFO_UNDERFLOW(i))
+			pr_cont("%u:%s ", i,
+				tidss->dispc_ops->plane_name(tidss->dispc, i));
+	}
+
+	pr_cont("(%016llx)\n", irqstatus);
+}
+
+static void tidss_irq_ocp_error_handler(struct drm_device *ddev,
+					u64 irqstatus)
+{
+	if (irqstatus & DSS_IRQ_DEVICE_OCP_ERR)
+		dev_err_ratelimited(ddev->dev, "OCP error\n");
+}
+
+irqreturn_t tidss_irq_handler(int irq, void *arg)
+{
+	struct drm_device *ddev = (struct drm_device *) arg;
+	struct tidss_device *tidss = ddev->dev_private;
+	unsigned int id;
+	u64 irqstatus;
+
+	if (WARN_ON(!ddev->irq_enabled))
+		return IRQ_NONE;
+
+	irqstatus = tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc);
+
+	for (id = 0; id < tidss->num_crtcs; id++) {
+		struct drm_crtc *crtc = tidss->crtcs[id];
+		struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+		u32 hw_videoport = tcrtc->hw_videoport;
+
+		if (irqstatus & (DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+				 DSS_IRQ_VP_VSYNC_ODD(hw_videoport)))
+			tidss_crtc_vblank_irq(crtc);
+
+		if (irqstatus & (DSS_IRQ_VP_FRAME_DONE(hw_videoport)))
+			tidss_crtc_framedone_irq(crtc);
+
+		if (irqstatus & DSS_IRQ_VP_SYNC_LOST(hw_videoport))
+			tidss_crtc_error_irq(crtc, irqstatus);
+	}
+
+	tidss_irq_ocp_error_handler(ddev, irqstatus);
+	tidss_irq_fifo_underflow(tidss, irqstatus);
+
+	return IRQ_HANDLED;
+}
+
+void tidss_irq_preinstall(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+
+	spin_lock_init(&tidss->wait_lock);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	tidss->dispc_ops->write_irqenable(tidss->dispc, 0);
+	tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+int tidss_irq_postinstall(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+	unsigned int i;
+	unsigned long flags;
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+
+	tidss->irq_mask = DSS_IRQ_DEVICE_OCP_ERR;
+
+	tidss->irq_uf_mask = 0;
+	for (i = 0; i < tidss->num_planes; ++i) {
+		struct tidss_plane *tplane = to_tidss_plane(tidss->planes[i]);
+
+		tidss->irq_uf_mask |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(tplane->hw_plane_id);
+	}
+	tidss->irq_mask |= tidss->irq_uf_mask;
+
+	for (i = 0; i < tidss->num_crtcs; ++i) {
+		struct tidss_crtc *tcrtc = to_tidss_crtc(tidss->crtcs[i]);
+
+		tidss->irq_mask |= DSS_IRQ_VP_SYNC_LOST(tcrtc->hw_videoport);
+
+		tidss->irq_mask |= DSS_IRQ_VP_FRAME_DONE(tcrtc->hw_videoport);
+	}
+
+	tidss_irq_update(ddev);
+
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+
+	return 0;
+}
+
+void tidss_irq_uninstall(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+	tidss->dispc_ops->write_irqenable(tidss->dispc, 0);
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+void tidss_irq_resume(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+	tidss_irq_update(ddev);
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
diff --git a/drivers/gpu/drm/tidss/tidss_irq.h b/drivers/gpu/drm/tidss/tidss_irq.h
new file mode 100644
index 0000000..e150729
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_irq.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_IRQ_H__
+#define __TIDSS_IRQ_H__
+
+#include <linux/types.h>
+
+struct drm_crtc;
+struct drm_device;
+
+void tidss_irq_enable_vblank(struct drm_crtc *crtc);
+void tidss_irq_disable_vblank(struct drm_crtc *crtc);
+
+void tidss_irq_preinstall(struct drm_device *ddev);
+int tidss_irq_postinstall(struct drm_device *ddev);
+void tidss_irq_uninstall(struct drm_device *ddev);
+irqreturn_t tidss_irq_handler(int irq, void *arg);
+
+void tidss_irq_resume(struct drm_device *ddev);
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
new file mode 100644
index 0000000..bbf2c48
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_kms.h"
+#include "tidss_plane.h"
+
+static void tidss_atomic_commit_tail(struct drm_atomic_state *old_state)
+{
+	struct drm_device *ddev = old_state->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	drm_atomic_helper_commit_modeset_disables(ddev, old_state);
+	drm_atomic_helper_commit_planes(ddev, old_state, 0);
+	drm_atomic_helper_commit_modeset_enables(ddev, old_state);
+
+	drm_atomic_helper_commit_hw_done(old_state);
+	drm_atomic_helper_wait_for_flip_done(ddev, old_state);
+
+	drm_atomic_helper_cleanup_planes(ddev, old_state);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
+	.atomic_commit_tail = tidss_atomic_commit_tail,
+};
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int tidss_modeset_init_properties(struct tidss_device *tidss)
+{
+	return 0;
+}
+
+int tidss_modeset_init(struct tidss_device *tidss)
+{
+	struct drm_device *ddev = tidss->ddev;
+	unsigned int i;
+	int ret;
+
+	dev_dbg(tidss->dev, "%s\n", __func__);
+
+	drm_mode_config_init(ddev);
+
+	ddev->mode_config.min_width = 8;
+	ddev->mode_config.min_height = 8;
+	ddev->mode_config.max_width = 8096;
+	ddev->mode_config.max_height = 8096;
+	ddev->mode_config.normalize_zpos = true;
+	ddev->mode_config.funcs = &mode_config_funcs;
+	ddev->mode_config.helper_private = &mode_config_helper_funcs;
+
+	ret = tidss_modeset_init_properties(tidss);
+	if (ret < 0)
+		return ret;
+
+	ret = tidss->dispc_ops->modeset_init(tidss->dispc);
+	if (ret)
+		return ret;
+
+	ret = drm_vblank_init(ddev, tidss->num_crtcs);
+	if (ret)
+		return ret;
+
+	/* Start with vertical blanking interrupt reporting disabled. */
+	for (i = 0; i < tidss->num_crtcs; ++i)
+		drm_crtc_vblank_reset(tidss->crtcs[i]);
+
+	drm_mode_config_reset(ddev);
+
+	dev_dbg(tidss->dev, "%s done\n", __func__);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/tidss/tidss_kms.h b/drivers/gpu/drm/tidss/tidss_kms.h
new file mode 100644
index 0000000..99aaff09
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_kms.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_KMS_H__
+#define __TIDSS_KMS_H__
+
+struct tidss_device;
+
+int tidss_modeset_init(struct tidss_device *tidss);
+
+#endif
diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c
new file mode 100644
index 0000000..c7d8c70
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_plane.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_plane.h"
+
+dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_cma_object *gem;
+	u32 x = state->src_x >> 16;
+	u32 y = state->src_y >> 16;
+
+	gem = drm_fb_cma_get_gem_obj(state->fb, 0);
+
+	return gem->paddr + fb->offsets[0] + x * fb->format->cpp[0] +
+		y * fb->pitches[0];
+}
+
+dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_cma_object *gem;
+	u32 x = state->src_x >> 16;
+	u32 y = state->src_y >> 16;
+
+	if (WARN_ON(state->fb->format->num_planes != 2))
+		return 0;
+
+	gem = drm_fb_cma_get_gem_obj(fb, 1);
+
+	return gem->paddr + fb->offsets[1] +
+		(x * fb->format->cpp[1] / fb->format->hsub) +
+		(y * fb->pitches[1] / fb->format->vsub);
+}
+
+static int tidss_plane_atomic_check(struct drm_plane *plane,
+				    struct drm_plane_state *state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct drm_crtc_state *crtc_state;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	const struct drm_format_info *finfo;
+	u32 hw_videoport;
+	int ret;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	if (!state->crtc) {
+		/*
+		 * The visible field is not reset by the DRM core but only
+		 * updated by drm_plane_helper_check_state(), set it manually.
+		 */
+		state->visible = false;
+		return 0;
+	}
+
+	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	ret = drm_atomic_helper_check_plane_state(state, crtc_state,
+						  0,
+						  INT_MAX,
+						  true, true);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * The HW is only able to start drawing at subpixel boundary
+	 * (the two first checks bellow). At the end of a row the HW
+	 * can only jump integer number of subpixels forward to
+	 * beginning of next row. So we can only show picture with
+	 * integer subpixel width (the third check). However, after
+	 * reaching the end of the drawn picture the drawing starts
+	 * again at the absolute memory address where top left corner
+	 * position of the drawn picture is (so there is no need to
+	 * check for odd height).
+	 */
+
+	finfo = drm_format_info(state->fb->format->format);
+
+	if ((state->src_x >> 16) % finfo->hsub != 0) {
+		dev_dbg(ddev->dev,
+			"%s: x-position %u not divisible subpixel size %u\n",
+			__func__, (state->src_x >> 16), finfo->hsub);
+		return -EINVAL;
+	}
+
+	if ((state->src_y >> 16) % finfo->vsub != 0) {
+		dev_dbg(ddev->dev,
+			"%s: y-position %u not divisible subpixel size %u\n",
+			__func__, (state->src_y >> 16), finfo->vsub);
+		return -EINVAL;
+	}
+
+	if ((state->src_w >> 16) % finfo->hsub != 0) {
+		dev_dbg(ddev->dev,
+			"%s: src width %u not divisible by subpixel size %u\n",
+			 __func__, (state->src_w >> 16), finfo->hsub);
+		return -EINVAL;
+	}
+
+	if (!state->visible)
+		return 0;
+
+	hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport;
+
+	return tidss->dispc_ops->plane_check(tidss->dispc,
+					     tplane->hw_plane_id,
+					     state, hw_videoport);
+}
+
+static void tidss_plane_atomic_update(struct drm_plane *plane,
+				      struct drm_plane_state *old_state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	struct drm_plane_state *state = plane->state;
+	u32 hw_videoport;
+	int ret;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	if (!state->visible) {
+		tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id,
+					       false);
+		return;
+	}
+
+	hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport;
+
+	ret = tidss->dispc_ops->plane_setup(tidss->dispc, tplane->hw_plane_id,
+					    state, hw_videoport);
+
+	if (ret) {
+		dev_err(plane->dev->dev, "Failed to setup plane %d\n",
+			tplane->hw_plane_id);
+		tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id,
+					       false);
+		return;
+	}
+
+	tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, true);
+}
+
+static void tidss_plane_atomic_disable(struct drm_plane *plane,
+				       struct drm_plane_state *old_state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, false);
+}
+
+static const struct drm_plane_helper_funcs tidss_plane_helper_funcs = {
+	.atomic_check = tidss_plane_atomic_check,
+	.atomic_update = tidss_plane_atomic_update,
+	.atomic_disable = tidss_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs tidss_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.reset = drm_atomic_helper_plane_reset,
+	.destroy = drm_plane_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
+				       u32 hw_plane_id,	u32 plane_type,
+				       u32 crtc_mask, const u32 *formats,
+				       u32 num_formats)
+{
+	const struct tidss_plane_feat *pfeat;
+	struct tidss_plane *tplane;
+	enum drm_plane_type type;
+	u32 possible_crtcs;
+	u32 num_planes = tidss->dispc_ops->get_num_planes(tidss->dispc);
+	int ret;
+
+	pfeat = tidss->dispc_ops->plane_feat(tidss->dispc, hw_plane_id);
+
+	tplane = devm_kzalloc(tidss->dev, sizeof(*tplane), GFP_KERNEL);
+	if (!tplane)
+		return ERR_PTR(-ENOMEM);
+
+	tplane->hw_plane_id = hw_plane_id;
+
+	possible_crtcs = crtc_mask;
+	type = plane_type;
+
+	ret = drm_universal_plane_init(tidss->ddev, &tplane->plane,
+				       possible_crtcs,
+				       &tidss_plane_funcs,
+				       formats, num_formats,
+				       NULL, type, NULL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	drm_plane_helper_add(&tplane->plane, &tidss_plane_helper_funcs);
+	if (num_planes > 1)
+		drm_plane_create_zpos_property(&tplane->plane, hw_plane_id, 0,
+					       num_planes - 1);
+
+	ret = drm_plane_create_color_properties(&tplane->plane,
+						pfeat->color.encodings,
+						pfeat->color.ranges,
+						pfeat->color.default_encoding,
+						pfeat->color.default_range);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (pfeat->blend.global_alpha) {
+		ret = drm_plane_create_alpha_property(&tplane->plane);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	return tplane;
+}
diff --git a/drivers/gpu/drm/tidss/tidss_plane.h b/drivers/gpu/drm/tidss/tidss_plane.h
new file mode 100644
index 0000000..ec600a9
--- /dev/null
+++ b/drivers/gpu/drm/tidss/tidss_plane.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_PLANE_H__
+#define __TIDSS_PLANE_H__
+
+#include "tidss_dispc.h"
+
+#define to_tidss_plane(p) container_of((p), struct tidss_plane, plane)
+
+struct tidss_plane {
+	struct drm_plane plane;
+
+	u32 hw_plane_id;
+
+};
+
+struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
+				       u32 hw_plane_id,	u32 plane_type,
+				       u32 crtc_mask, const u32 *formats,
+				       u32 num_formats);
+
+dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state);
+dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state);
+
+#endif
