diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst
index 35d673bf9b56..c36586dad29d 100644
--- a/Documentation/gpu/index.rst
+++ b/Documentation/gpu/index.rst
@@ -15,6 +15,7 @@ Linux GPU Driver Developer's Guide
    pl111
    tegra
    tinydrm
+   tve200
    vc4
    vga-switcheroo
    vgaarbiter
diff --git a/Documentation/gpu/tve200.rst b/Documentation/gpu/tve200.rst
new file mode 100644
index 000000000000..69b17b324e12
--- /dev/null
+++ b/Documentation/gpu/tve200.rst
@@ -0,0 +1,6 @@
+==================================
+ drm/tve200 Faraday TV Encoder 200
+==================================
+
+.. kernel-doc:: drivers/gpu/drm/tve200/tve200_drv.c
+   :doc: Faraday TV Encoder 200
diff --git a/MAINTAINERS b/MAINTAINERS
index e87cba115ea4..c3d42d68253a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4305,6 +4305,12 @@ T:	git git://anongit.freedesktop.org/drm/drm-misc
 S:	Maintained
 F:	drivers/gpu/drm/bochs/
 
+DRM DRIVER FOR FARADAY TVE200 TV ENCODER
+M:	Linus Walleij <linus.walleij@linaro.org>
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+S:	Maintained
+F:	drivers/gpu/drm/tve200/
+
 DRM DRIVER FOR INTEL I810 VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/i810/
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 83cb2a88c204..c5e1a8409285 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -278,6 +278,8 @@ source "drivers/gpu/drm/tinydrm/Kconfig"
 
 source "drivers/gpu/drm/pl111/Kconfig"
 
+source "drivers/gpu/drm/tve200/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 24a066e1841c..cc81813e2238 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -100,3 +100,4 @@ obj-$(CONFIG_DRM_ZTE)	+= zte/
 obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
 obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
 obj-$(CONFIG_DRM_PL111) += pl111/
+obj-$(CONFIG_DRM_TVE200) += tve200/
diff --git a/drivers/gpu/drm/tve200/Kconfig b/drivers/gpu/drm/tve200/Kconfig
new file mode 100644
index 000000000000..21d9841ddb88
--- /dev/null
+++ b/drivers/gpu/drm/tve200/Kconfig
@@ -0,0 +1,15 @@
+config DRM_TVE200
+	tristate "DRM Support for Faraday TV Encoder TVE200"
+	depends on DRM
+	depends on CMA
+	depends on ARM || COMPILE_TEST
+	depends on OF
+	select DRM_PANEL
+	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 Faraday TV Encoder
+	  TVE200 Controller.
+	  If M is selected the module will be called tve200_drm.
diff --git a/drivers/gpu/drm/tve200/Makefile b/drivers/gpu/drm/tve200/Makefile
new file mode 100644
index 000000000000..a9dba54f7ee5
--- /dev/null
+++ b/drivers/gpu/drm/tve200/Makefile
@@ -0,0 +1,5 @@
+tve200_drm-y +=	tve200_connector.o \
+		tve200_display.o \
+		tve200_drv.o
+
+obj-$(CONFIG_DRM_TVE200) += tve200_drm.o
diff --git a/drivers/gpu/drm/tve200/tve200_connector.c b/drivers/gpu/drm/tve200/tve200_connector.c
new file mode 100644
index 000000000000..5e6c20b57d6d
--- /dev/null
+++ b/drivers/gpu/drm/tve200/tve200_connector.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (C) 2006-2008 Intel Corporation
+ * Copyright (C) 2007 Amos Lee <amos_lee@storlinksemi.com>
+ * Copyright (C) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * Copyright (C) 2017 Eric Anholt
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ */
+
+/**
+ * tve200_drm_connector.c
+ * Implementation of the connector functions for the Faraday TV Encoder
+ */
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "tve200_drm.h"
+
+static void tve200_connector_destroy(struct drm_connector *connector)
+{
+	struct tve200_drm_connector *tve200con =
+		to_tve200_connector(connector);
+
+	if (tve200con->panel)
+		drm_panel_detach(tve200con->panel);
+
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status tve200_connector_detect(struct drm_connector
+							*connector, bool force)
+{
+	struct tve200_drm_connector *tve200con =
+		to_tve200_connector(connector);
+
+	return (tve200con->panel ?
+		connector_status_connected :
+		connector_status_disconnected);
+}
+
+static int tve200_connector_helper_get_modes(struct drm_connector *connector)
+{
+	struct tve200_drm_connector *tve200con =
+		to_tve200_connector(connector);
+
+	if (!tve200con->panel)
+		return 0;
+
+	return drm_panel_get_modes(tve200con->panel);
+}
+
+static const struct drm_connector_funcs connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = tve200_connector_destroy,
+	.detect = tve200_connector_detect,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+	.get_modes = tve200_connector_helper_get_modes,
+};
+
+/*
+ * Walks the OF graph to find the panel node and then asks DRM to look
+ * up the panel.
+ */
+static struct drm_panel *tve200_get_panel(struct device *dev)
+{
+	struct device_node *endpoint, *panel_node;
+	struct device_node *np = dev->of_node;
+	struct drm_panel *panel;
+
+	endpoint = of_graph_get_next_endpoint(np, NULL);
+	if (!endpoint) {
+		dev_err(dev, "no endpoint to fetch panel\n");
+		return NULL;
+	}
+
+	/* Don't proceed if we have an endpoint but no panel_node tied to it */
+	panel_node = of_graph_get_remote_port_parent(endpoint);
+	of_node_put(endpoint);
+	if (!panel_node) {
+		dev_err(dev, "no valid panel node\n");
+		return NULL;
+	}
+
+	panel = of_drm_find_panel(panel_node);
+	of_node_put(panel_node);
+
+	return panel;
+}
+
+int tve200_connector_init(struct drm_device *dev)
+{
+	struct tve200_drm_dev_private *priv = dev->dev_private;
+	struct tve200_drm_connector *tve200con = &priv->connector;
+	struct drm_connector *connector = &tve200con->connector;
+
+	drm_connector_init(dev, connector, &connector_funcs,
+			   DRM_MODE_CONNECTOR_DPI);
+	drm_connector_helper_add(connector, &connector_helper_funcs);
+
+	tve200con->panel = tve200_get_panel(dev->dev);
+	if (tve200con->panel)
+		drm_panel_attach(tve200con->panel, connector);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/tve200/tve200_display.c b/drivers/gpu/drm/tve200/tve200_display.c
new file mode 100644
index 000000000000..37fb333331f3
--- /dev/null
+++ b/drivers/gpu/drm/tve200/tve200_display.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (C) 2006-2008 Intel Corporation
+ * Copyright (C) 2007 Amos Lee <amos_lee@storlinksemi.com>
+ * Copyright (C) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * Copyright (C) 2017 Eric Anholt
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ */
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/dma-buf.h>
+#include <linux/of_graph.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+
+#include "tve200_drm.h"
+
+irqreturn_t tve200_irq(int irq, void *data)
+{
+	struct tve200_drm_dev_private *priv = data;
+	u32 stat;
+	u32 val;
+
+	stat = readl(priv->regs + TVE200_INT_STAT);
+
+	if (!stat)
+		return IRQ_NONE;
+
+	/*
+	 * Vblank IRQ
+	 *
+	 * The hardware is a bit tilted: the line stays high after clearing
+	 * the vblank IRQ, firing many more interrupts. We counter this
+	 * by toggling the IRQ back and forth from firing at vblank and
+	 * firing at start of active image, which works around the problem
+	 * since those occur strictly in sequence, and we get two IRQs for each
+	 * frame, one at start of Vblank (that we make call into the CRTC) and
+	 * another one at the start of the image (that we discard).
+	 */
+	if (stat & TVE200_INT_V_STATUS) {
+		val = readl(priv->regs + TVE200_CTRL);
+		/* We have an actual start of vsync */
+		if (!(val & TVE200_VSTSTYPE_BITS)) {
+			drm_crtc_handle_vblank(&priv->pipe.crtc);
+			/* Toggle trigger to start of active image */
+			val |= TVE200_VSTSTYPE_VAI;
+		} else {
+			/* Toggle trigger back to start of vsync */
+			val &= ~TVE200_VSTSTYPE_BITS;
+		}
+		writel(val, priv->regs + TVE200_CTRL);
+	} else
+		dev_err(priv->drm->dev, "stray IRQ %08x\n", stat);
+
+	/* Clear the interrupt once done */
+	writel(stat, priv->regs + TVE200_INT_CLR);
+
+	return IRQ_HANDLED;
+}
+
+static int tve200_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;
+
+	/*
+	 * We support these specific resolutions and nothing else.
+	 */
+	if (!(mode->hdisplay == 352 && mode->vdisplay == 240) && /* SIF(525) */
+	    !(mode->hdisplay == 352 && mode->vdisplay == 288) && /* CIF(625) */
+	    !(mode->hdisplay == 640 && mode->vdisplay == 480) && /* VGA */
+	    !(mode->hdisplay == 720 && mode->vdisplay == 480) && /* D1 */
+	    !(mode->hdisplay == 720 && mode->vdisplay == 576)) { /* D1 */
+		DRM_DEBUG_KMS("unsupported display mode (%u x %u)\n",
+			mode->hdisplay, mode->vdisplay);
+		return -EINVAL;
+	}
+
+	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 void tve200_display_enable(struct drm_simple_display_pipe *pipe,
+				 struct drm_crtc_state *cstate)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_plane *plane = &pipe->plane;
+	struct drm_device *drm = crtc->dev;
+	struct tve200_drm_dev_private *priv = drm->dev_private;
+	const struct drm_display_mode *mode = &cstate->mode;
+	struct drm_framebuffer *fb = plane->state->fb;
+	struct drm_connector *connector = &priv->connector.connector;
+	u32 format = fb->format->format;
+	u32 ctrl1 = 0;
+
+	clk_prepare_enable(priv->clk);
+
+	/* Function 1 */
+	ctrl1 |= TVE200_CTRL_CSMODE;
+	/* Interlace mode for CCIR656: parameterize? */
+	ctrl1 |= TVE200_CTRL_NONINTERLACE;
+	/* 32 words per burst */
+	ctrl1 |= TVE200_CTRL_BURST_32_WORDS;
+	/* 16 retries */
+	ctrl1 |= TVE200_CTRL_RETRYCNT_16;
+	/* NTSC mode: parametrize? */
+	ctrl1 |= TVE200_CTRL_NTSC;
+
+	/* Vsync IRQ at start of Vsync at first */
+	ctrl1 |= TVE200_VSTSTYPE_VSYNC;
+
+	if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+		ctrl1 |= TVE200_CTRL_TVCLKP;
+
+	if ((mode->hdisplay == 352 && mode->vdisplay == 240) || /* SIF(525) */
+	    (mode->hdisplay == 352 && mode->vdisplay == 288)) { /* CIF(625) */
+		ctrl1 |= TVE200_CTRL_IPRESOL_CIF;
+		dev_info(drm->dev, "CIF mode\n");
+	} else if (mode->hdisplay == 640 && mode->vdisplay == 480) {
+		ctrl1 |= TVE200_CTRL_IPRESOL_VGA;
+		dev_info(drm->dev, "VGA mode\n");
+	} else if ((mode->hdisplay == 720 && mode->vdisplay == 480) ||
+		   (mode->hdisplay == 720 && mode->vdisplay == 576)) {
+		ctrl1 |= TVE200_CTRL_IPRESOL_D1;
+		dev_info(drm->dev, "D1 mode\n");
+	}
+
+	if (format & DRM_FORMAT_BIG_ENDIAN) {
+		ctrl1 |= TVE200_CTRL_BBBP;
+		format &= ~DRM_FORMAT_BIG_ENDIAN;
+	}
+
+	switch (format) {
+	case DRM_FORMAT_XRGB8888:
+		ctrl1 |= TVE200_IPDMOD_RGB888;
+		break;
+	case DRM_FORMAT_RGB565:
+		ctrl1 |= TVE200_IPDMOD_RGB565;
+		break;
+	case DRM_FORMAT_XRGB1555:
+		ctrl1 |= TVE200_IPDMOD_RGB555;
+		break;
+	case DRM_FORMAT_XBGR8888:
+		ctrl1 |= TVE200_IPDMOD_RGB888 | TVE200_BGR;
+		break;
+	case DRM_FORMAT_BGR565:
+		ctrl1 |= TVE200_IPDMOD_RGB565 | TVE200_BGR;
+		break;
+	case DRM_FORMAT_XBGR1555:
+		ctrl1 |= TVE200_IPDMOD_RGB555 | TVE200_BGR;
+		break;
+	case DRM_FORMAT_YUYV:
+		ctrl1 |= TVE200_IPDMOD_YUV422;
+		ctrl1 |= TVE200_CTRL_YCBCRODR_CR0Y1CB0Y0;
+		break;
+	case DRM_FORMAT_YVYU:
+		ctrl1 |= TVE200_IPDMOD_YUV422;
+		ctrl1 |= TVE200_CTRL_YCBCRODR_CB0Y1CR0Y0;
+		break;
+	case DRM_FORMAT_UYVY:
+		ctrl1 |= TVE200_IPDMOD_YUV422;
+		ctrl1 |= TVE200_CTRL_YCBCRODR_Y1CR0Y0CB0;
+		break;
+	case DRM_FORMAT_VYUY:
+		ctrl1 |= TVE200_IPDMOD_YUV422;
+		ctrl1 |= TVE200_CTRL_YCBCRODR_Y1CB0Y0CR0;
+		break;
+	case DRM_FORMAT_YUV420:
+		ctrl1 |= TVE200_CTRL_YUV420;
+		ctrl1 |= TVE200_IPDMOD_YUV420;
+		break;
+	default:
+		dev_err(drm->dev, "Unknown FB format 0x%08x\n",
+			fb->format->format);
+		break;
+	}
+
+	ctrl1 |= TVE200_TVEEN;
+
+	drm_panel_prepare(priv->connector.panel);
+
+	/* Turn it on */
+	writel(ctrl1, priv->regs + TVE200_CTRL);
+
+	drm_panel_enable(priv->connector.panel);
+
+	drm_crtc_vblank_on(crtc);
+}
+
+void tve200_display_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_device *drm = crtc->dev;
+	struct tve200_drm_dev_private *priv = drm->dev_private;
+
+	drm_crtc_vblank_off(crtc);
+
+	drm_panel_disable(priv->connector.panel);
+
+	/* Disable and Power Down */
+	writel(0, priv->regs + TVE200_CTRL);
+
+	drm_panel_unprepare(priv->connector.panel);
+
+	clk_disable_unprepare(priv->clk);
+}
+
+static void tve200_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 tve200_drm_dev_private *priv = 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;
+
+	if (fb) {
+		/* For RGB, the Y component is used as base address */
+		writel(drm_fb_cma_get_gem_addr(fb, pstate, 0),
+		       priv->regs + TVE200_Y_FRAME_BASE_ADDR);
+
+		/* For three plane YUV we need two more addresses */
+		if (fb->format->format == DRM_FORMAT_YUV420) {
+			writel(drm_fb_cma_get_gem_addr(fb, pstate, 1),
+			       priv->regs + TVE200_U_FRAME_BASE_ADDR);
+			writel(drm_fb_cma_get_gem_addr(fb, pstate, 2),
+			       priv->regs + TVE200_V_FRAME_BASE_ADDR);
+		}
+	}
+
+	if (event) {
+		crtc->state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+int tve200_enable_vblank(struct drm_device *drm, unsigned int crtc)
+{
+	struct tve200_drm_dev_private *priv = drm->dev_private;
+
+	writel(TVE200_INT_V_STATUS, priv->regs + TVE200_INT_EN);
+	return 0;
+}
+
+void tve200_disable_vblank(struct drm_device *drm, unsigned int crtc)
+{
+	struct tve200_drm_dev_private *priv = drm->dev_private;
+
+	writel(0, priv->regs + TVE200_INT_EN);
+}
+
+static int tve200_display_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state)
+{
+	return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
+}
+
+const struct drm_simple_display_pipe_funcs tve200_display_funcs = {
+	.check = tve200_display_check,
+	.enable = tve200_display_enable,
+	.disable = tve200_display_disable,
+	.update = tve200_display_update,
+	.prepare_fb = tve200_display_prepare_fb,
+};
+
+int tve200_display_init(struct drm_device *drm)
+{
+	struct tve200_drm_dev_private *priv = drm->dev_private;
+	int ret;
+	static const u32 formats[] = {
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_XBGR8888,
+		DRM_FORMAT_RGB565,
+		DRM_FORMAT_BGR565,
+		DRM_FORMAT_XRGB1555,
+		DRM_FORMAT_XBGR1555,
+		/*
+		 * The controller actually supports any YCbCr ordering,
+		 * for packed YCbCr. This just lists the orderings that
+		 * DRM supports.
+		 */
+		DRM_FORMAT_YUYV,
+		DRM_FORMAT_YVYU,
+		DRM_FORMAT_UYVY,
+		DRM_FORMAT_VYUY,
+		/* This uses three planes */
+		DRM_FORMAT_YUV420,
+	};
+
+	ret = drm_simple_display_pipe_init(drm, &priv->pipe,
+					   &tve200_display_funcs,
+					   formats, ARRAY_SIZE(formats),
+					   &priv->connector.connector);
+	if (ret)
+		return ret;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/tve200/tve200_drm.h b/drivers/gpu/drm/tve200/tve200_drm.h
new file mode 100644
index 000000000000..f00fc47a6bd1
--- /dev/null
+++ b/drivers/gpu/drm/tve200/tve200_drm.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (C) 2006-2008 Intel Corporation
+ * Copyright (C) 2007 Amos Lee <amos_lee@storlinksemi.com>
+ * Copyright (C) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * Copyright (C) 2017 Eric Anholt
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ */
+
+#ifndef _TVE200_DRM_H_
+#define _TVE200_DRM_H_
+
+/* Bits 2-31 are valid physical base addresses */
+#define TVE200_Y_FRAME_BASE_ADDR	0x00
+#define TVE200_U_FRAME_BASE_ADDR	0x04
+#define TVE200_V_FRAME_BASE_ADDR	0x08
+
+#define TVE200_INT_EN			0x0C
+#define TVE200_INT_CLR			0x10
+#define TVE200_INT_STAT			0x14
+#define TVE200_INT_BUS_ERR		BIT(7)
+#define TVE200_INT_V_STATUS		BIT(6) /* vertical blank */
+#define TVE200_INT_V_NEXT_FRAME		BIT(5)
+#define TVE200_INT_U_NEXT_FRAME		BIT(4)
+#define TVE200_INT_Y_NEXT_FRAME		BIT(3)
+#define TVE200_INT_V_FIFO_UNDERRUN	BIT(2)
+#define TVE200_INT_U_FIFO_UNDERRUN	BIT(1)
+#define TVE200_INT_Y_FIFO_UNDERRUN	BIT(0)
+#define TVE200_FIFO_UNDERRUNS		(TVE200_INT_V_FIFO_UNDERRUN | \
+					 TVE200_INT_U_FIFO_UNDERRUN | \
+					 TVE200_INT_Y_FIFO_UNDERRUN)
+
+#define TVE200_CTRL			0x18
+#define TVE200_CTRL_YUV420		BIT(31)
+#define TVE200_CTRL_CSMODE		BIT(30)
+#define TVE200_CTRL_NONINTERLACE	BIT(28) /* 0 = non-interlace CCIR656 */
+#define TVE200_CTRL_TVCLKP		BIT(27) /* Inverted clock phase */
+/* Bits 24..26 define the burst size after arbitration on the bus */
+#define TVE200_CTRL_BURST_4_WORDS	(0 << 24)
+#define TVE200_CTRL_BURST_8_WORDS	(1 << 24)
+#define TVE200_CTRL_BURST_16_WORDS	(2 << 24)
+#define TVE200_CTRL_BURST_32_WORDS	(3 << 24)
+#define TVE200_CTRL_BURST_64_WORDS	(4 << 24)
+#define TVE200_CTRL_BURST_128_WORDS	(5 << 24)
+#define TVE200_CTRL_BURST_256_WORDS	(6 << 24)
+#define TVE200_CTRL_BURST_0_WORDS	(7 << 24) /* ? */
+/*
+ * Bits 16..23 is the retry count*16 before issueing a new AHB transfer
+ * on the AHB bus.
+ */
+#define TVE200_CTRL_RETRYCNT_MASK	GENMASK(23, 16)
+#define TVE200_CTRL_RETRYCNT_16		(1 << 16)
+#define TVE200_CTRL_BBBP		BIT(15) /* 0 = little-endian */
+/* Bits 12..14 define the YCbCr ordering */
+#define TVE200_CTRL_YCBCRODR_CB0Y0CR0Y1	(0 << 12)
+#define TVE200_CTRL_YCBCRODR_Y0CB0Y1CR0	(1 << 12)
+#define TVE200_CTRL_YCBCRODR_CR0Y0CB0Y1	(2 << 12)
+#define TVE200_CTRL_YCBCRODR_Y1CB0Y0CR0	(3 << 12)
+#define TVE200_CTRL_YCBCRODR_CR0Y1CB0Y0	(4 << 12)
+#define TVE200_CTRL_YCBCRODR_Y1CR0Y0CB0	(5 << 12)
+#define TVE200_CTRL_YCBCRODR_CB0Y1CR0Y0	(6 << 12)
+#define TVE200_CTRL_YCBCRODR_Y0CR0Y1CB0	(7 << 12)
+/* Bits 10..11 define the input resolution (framebuffer size) */
+#define TVE200_CTRL_IPRESOL_CIF		(0 << 10)
+#define TVE200_CTRL_IPRESOL_VGA		(1 << 10)
+#define TVE200_CTRL_IPRESOL_D1		(2 << 10)
+#define TVE200_CTRL_NTSC		BIT(9) /* 0 = PAL, 1 = NTSC */
+#define TVE200_CTRL_INTERLACE		BIT(8) /* 1 = interlace, only for D1 */
+#define TVE200_IPDMOD_RGB555		(0 << 6) /* TVE200_CTRL_YUV420 = 0 */
+#define TVE200_IPDMOD_RGB565		(1 << 6)
+#define TVE200_IPDMOD_RGB888		(2 << 6)
+#define TVE200_IPDMOD_YUV420		(2 << 6) /* TVE200_CTRL_YUV420 = 1 */
+#define TVE200_IPDMOD_YUV422		(3 << 6)
+/* Bits 4 & 5 define when to fire the vblank IRQ */
+#define TVE200_VSTSTYPE_VSYNC		(0 << 4) /* start of vsync */
+#define TVE200_VSTSTYPE_VBP		(1 << 4) /* start of v back porch */
+#define TVE200_VSTSTYPE_VAI		(2 << 4) /* start of v active image */
+#define TVE200_VSTSTYPE_VFP		(3 << 4) /* start of v front porch */
+#define TVE200_VSTSTYPE_BITS		(BIT(4) | BIT(5))
+#define TVE200_BGR			BIT(1) /* 0 = RGB, 1 = BGR */
+#define TVE200_TVEEN			BIT(0) /* Enable TVE block */
+
+#define TVE200_CTRL_2			0x1c
+#define TVE200_CTRL_3			0x20
+
+#define TVE200_CTRL_4			0x24
+#define TVE200_CTRL_4_RESET		BIT(0) /* triggers reset of TVE200 */
+
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct tve200_drm_connector {
+	struct drm_connector connector;
+	struct drm_panel *panel;
+};
+
+struct tve200_drm_dev_private {
+	struct drm_device *drm;
+
+	struct tve200_drm_connector connector;
+	struct drm_simple_display_pipe pipe;
+	struct drm_fbdev_cma *fbdev;
+
+	void *regs;
+	struct clk *pclk;
+	struct clk *clk;
+};
+
+#define to_tve200_connector(x) \
+	container_of(x, struct tve200_drm_connector, connector)
+
+int tve200_display_init(struct drm_device *dev);
+int tve200_enable_vblank(struct drm_device *drm, unsigned int crtc);
+void tve200_disable_vblank(struct drm_device *drm, unsigned int crtc);
+irqreturn_t tve200_irq(int irq, void *data);
+int tve200_connector_init(struct drm_device *dev);
+int tve200_encoder_init(struct drm_device *dev);
+int tve200_dumb_create(struct drm_file *file_priv,
+		      struct drm_device *dev,
+		      struct drm_mode_create_dumb *args);
+
+#endif /* _TVE200_DRM_H_ */
diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c
new file mode 100644
index 000000000000..fe742106db4e
--- /dev/null
+++ b/drivers/gpu/drm/tve200/tve200_drv.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (C) 2006-2008 Intel Corporation
+ * Copyright (C) 2007 Amos Lee <amos_lee@storlinksemi.com>
+ * Copyright (C) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * Copyright (C) 2017 Eric Anholt
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ */
+
+/**
+ * DOC: Faraday TV Encoder TVE200 DRM Driver
+ *
+ * The Faraday TV Encoder TVE200 is also known as the Gemini TV Interface
+ * Controller (TVC) and is found in the Gemini Chipset from Storlink
+ * Semiconductor (later Storm Semiconductor, later Cortina Systems)
+ * but also in the Grain Media GM8180 chipset. On the Gemini the module
+ * is connected to 8 data lines and a single clock line, comprising an
+ * 8-bit BT.656 interface.
+ *
+ * This is a very basic YUV display driver. The datasheet specifies that
+ * it supports the ITU BT.656 standard. It requires a 27 MHz clock which is
+ * the hallmark of any TV encoder supporting both PAL and NTSC.
+ *
+ * This driver exposes a standard KMS interface for this TV encoder.
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-buf.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/shmem_fs.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_panel.h>
+
+#include "tve200_drm.h"
+
+#define DRIVER_DESC      "DRM module for Faraday TVE200"
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+	.fb_create = drm_fb_cma_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int tve200_modeset_init(struct drm_device *dev)
+{
+	struct drm_mode_config *mode_config;
+	struct tve200_drm_dev_private *priv = dev->dev_private;
+	int ret = 0;
+
+	drm_mode_config_init(dev);
+	mode_config = &dev->mode_config;
+	mode_config->funcs = &mode_config_funcs;
+	mode_config->min_width = 352;
+	mode_config->max_width = 720;
+	mode_config->min_height = 240;
+	mode_config->max_height = 576;
+
+	ret = tve200_connector_init(dev);
+	if (ret) {
+		dev_err(dev->dev, "Failed to create tve200_drm_connector\n");
+		goto out_config;
+	}
+
+	/*
+	 * Don't actually attach if we didn't find a drm_panel
+	 * attached to us.
+	 */
+	if (!priv->connector.panel) {
+		dev_info(dev->dev,
+			 "deferring due to lack of DRM panel device\n");
+		ret = -EPROBE_DEFER;
+		goto out_config;
+	}
+	dev_info(dev->dev, "attached to panel %s\n",
+		 dev_name(priv->connector.panel->dev));
+
+	ret = tve200_display_init(dev);
+	if (ret) {
+		dev_err(dev->dev, "failed to init display\n");
+		goto out_config;
+	}
+
+	ret = drm_vblank_init(dev, 1);
+	if (ret) {
+		dev_err(dev->dev, "failed to init vblank\n");
+		goto out_config;
+	}
+
+	drm_mode_config_reset(dev);
+
+	/*
+	 * Passing in 16 here will make the RGB656 mode the default
+	 * Passing in 32 will use XRGB8888 mode
+	 */
+	priv->fbdev = drm_fbdev_cma_init(dev, 16,
+					 dev->mode_config.num_connector);
+	drm_kms_helper_poll_init(dev);
+
+	goto finish;
+
+out_config:
+	drm_mode_config_cleanup(dev);
+finish:
+	return ret;
+}
+
+DEFINE_DRM_GEM_CMA_FOPS(drm_fops);
+
+static void tve200_lastclose(struct drm_device *dev)
+{
+	struct tve200_drm_dev_private *priv = dev->dev_private;
+
+	drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static struct drm_driver tve200_drm_driver = {
+	.driver_features =
+		DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC,
+	.lastclose = tve200_lastclose,
+	.ioctls = NULL,
+	.fops = &drm_fops,
+	.name = "tve200",
+	.desc = DRIVER_DESC,
+	.date = "20170703",
+	.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,
+
+	.enable_vblank = tve200_enable_vblank,
+	.disable_vblank = tve200_disable_vblank,
+
+	.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 tve200_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tve200_drm_dev_private *priv;
+	struct drm_device *drm;
+	struct resource *res;
+	int irq;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	drm = drm_dev_alloc(&tve200_drm_driver, dev);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+	platform_set_drvdata(pdev, drm);
+	priv->drm = drm;
+	drm->dev_private = priv;
+
+	/* Clock the silicon so we can access the registers */
+	priv->pclk = devm_clk_get(dev, "PCLK");
+	if (IS_ERR(priv->pclk)) {
+		dev_err(dev, "unable to get PCLK\n");
+		ret = PTR_ERR(priv->pclk);
+		goto dev_unref;
+	}
+	ret = clk_prepare_enable(priv->pclk);
+	if (ret) {
+		dev_err(dev, "failed to enable PCLK\n");
+		goto dev_unref;
+	}
+
+	/* This clock is for the pixels (27MHz) */
+	priv->clk = devm_clk_get(dev, "TVE");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev, "unable to get TVE clock\n");
+		ret = PTR_ERR(priv->clk);
+		goto clk_disable;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->regs = devm_ioremap_resource(dev, res);
+	if (!priv->regs) {
+		dev_err(dev, "%s failed mmio\n", __func__);
+		ret = -EINVAL;
+		goto clk_disable;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (!irq) {
+		ret = -EINVAL;
+		goto clk_disable;
+	}
+
+	/* turn off interrupts before requesting the irq */
+	writel(0, priv->regs + TVE200_INT_EN);
+
+	ret = devm_request_irq(dev, irq, tve200_irq, 0, "tve200", priv);
+	if (ret) {
+		dev_err(dev, "failed to request irq %d\n", ret);
+		goto clk_disable;
+	}
+
+	ret = tve200_modeset_init(drm);
+	if (ret)
+		goto clk_disable;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto clk_disable;
+
+	return 0;
+
+clk_disable:
+	clk_disable_unprepare(priv->pclk);
+dev_unref:
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static int tve200_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+	struct tve200_drm_dev_private *priv = drm->dev_private;
+
+	drm_dev_unregister(drm);
+	if (priv->fbdev)
+		drm_fbdev_cma_fini(priv->fbdev);
+	drm_mode_config_cleanup(drm);
+	clk_disable_unprepare(priv->pclk);
+	drm_dev_unref(drm);
+
+	return 0;
+}
+
+static const struct of_device_id tve200_of_match[] = {
+	{
+		.compatible = "faraday,tve200",
+	},
+	{},
+};
+
+static struct platform_driver tve200_driver = {
+	.driver = {
+		.name           = "tve200",
+		.of_match_table = of_match_ptr(tve200_of_match),
+	},
+	.probe = tve200_probe,
+	.remove = tve200_remove,
+};
+module_platform_driver(tve200_driver);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_LICENSE("GPL");
