All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v2 0/8] rmk's Dove DRM/TDA19988 Cubox driver
@ 2013-06-09 19:06 ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-09 19:06 UTC (permalink / raw)
  To: linux-arm-kernel

This is version 2 of my DRM driver.  Changes since the previous version:

1. It's now called Armada not Dove, because the "LCD controllers" aka CRTCs
   are found in Armada 16x as well as Armada 510 Dove.  The Armada 16x is
   preliminary work.

2. Cursor support is now a separate patch.  I've tried several formats,
   and 64x32 ARGB is the one I've settled on for the time being, though
   the driver will permit 32x64 as well.  This, however, is not available
   with Armada 16x hardware.  This patch will be a separate follow-up to
   this.  The reason for this is it's in a separate branch which conflicts
   with this (and my NXP-TDA19988 output driver branch.)  The value of this
   is still questionable due to the restrictions in the hardware meaning
   the entire cursor has to be reloaded near the edge of the screen.

   This also may interfere with the interlaced support; however the DRM
   i2c TDA998x driver seems incapable of supporting interlace at present.
   (It works with the CEA mode number programmed but not if you program
   with mode timings.)

3. A slave encoder output encoder which contains nothing specific to TDA19988
   nor Cubox.  This data is now in the top level driver, and can be provided
   using whatever method is desirable (eg, from DT.)

   What *can't* be done without a rewrite of the DRM slave encoder backend
   is to have the TDA998x I2C device provided by DT.  There are mumblings
   about redoing the slave encoder API, hopefully this will be fixed there.

4. Finished off the page flip support, which now gets used on mode changes
   as well.  Page flip as a whole still isn't tested.

5. Removal of default mode and reporting of unknown connector status.  The
   kernel side will select 1024x768 if no connectors are connected - this
   will cause problems if your display reports that it supports 1024x768
   but it doesn't actually.  Xorg will do similar too.

What is not in this patch set yet, but I have patches for:
1. Pulled the clock stuff out of the armada_crtc.c file into the top level.
   Armada 16x has different clocking options to Armada 510, and certain bits
   in the clock configuration register are different.  This needs to be
   dealt with like we do in AMBA devices with "variant" support.  So in a
   DT environment, we'd have:
	compatible = "marvell,armada510-lcd";
   or
	compatible = "marvell,armada16x-lcd";
   and the driver adjusts according to that.

2. Sebastian's additional patches

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [RFC v2 0/8] rmk's Dove DRM/TDA19988 Cubox driver
@ 2013-06-09 19:06 ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-09 19:06 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

This is version 2 of my DRM driver.  Changes since the previous version:

1. It's now called Armada not Dove, because the "LCD controllers" aka CRTCs
   are found in Armada 16x as well as Armada 510 Dove.  The Armada 16x is
   preliminary work.

2. Cursor support is now a separate patch.  I've tried several formats,
   and 64x32 ARGB is the one I've settled on for the time being, though
   the driver will permit 32x64 as well.  This, however, is not available
   with Armada 16x hardware.  This patch will be a separate follow-up to
   this.  The reason for this is it's in a separate branch which conflicts
   with this (and my NXP-TDA19988 output driver branch.)  The value of this
   is still questionable due to the restrictions in the hardware meaning
   the entire cursor has to be reloaded near the edge of the screen.

   This also may interfere with the interlaced support; however the DRM
   i2c TDA998x driver seems incapable of supporting interlace at present.
   (It works with the CEA mode number programmed but not if you program
   with mode timings.)

3. A slave encoder output encoder which contains nothing specific to TDA19988
   nor Cubox.  This data is now in the top level driver, and can be provided
   using whatever method is desirable (eg, from DT.)

   What *can't* be done without a rewrite of the DRM slave encoder backend
   is to have the TDA998x I2C device provided by DT.  There are mumblings
   about redoing the slave encoder API, hopefully this will be fixed there.

4. Finished off the page flip support, which now gets used on mode changes
   as well.  Page flip as a whole still isn't tested.

5. Removal of default mode and reporting of unknown connector status.  The
   kernel side will select 1024x768 if no connectors are connected - this
   will cause problems if your display reports that it supports 1024x768
   but it doesn't actually.  Xorg will do similar too.

What is not in this patch set yet, but I have patches for:
1. Pulled the clock stuff out of the armada_crtc.c file into the top level.
   Armada 16x has different clocking options to Armada 510, and certain bits
   in the clock configuration register are different.  This needs to be
   dealt with like we do in AMBA devices with "variant" support.  So in a
   DT environment, we'd have:
	compatible = "marvell,armada510-lcd";
   or
	compatible = "marvell,armada16x-lcd";
   and the driver adjusts according to that.

2. Sebastian's additional patches

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:32   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:29 UTC (permalink / raw)
  To: linux-arm-kernel; +Cc: Jason Cooper, dri-devel, Sebastian Hesselbarth

This patch adds support for the pair of LCD controllers on the Marvell
Armada 510 SoCs.  This driver supports:
- multiple contiguous scanout buffers for video and graphics
- shm backed cacheable buffer objects for X pixmaps for Vivante GPU
  acceleration
- dual lcd0 and lcd1 crt operation
- video overlay on each LCD crt
- page flipping of the main scanout buffers

Included in this commit is the core driver with no output support; output
support is platform and encoder driver dependent.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/Kconfig                 |    2 +
 drivers/gpu/drm/Makefile                |    1 +
 drivers/gpu/drm/armada/Kconfig          |   15 +
 drivers/gpu/drm/armada/Makefile         |    6 +
 drivers/gpu/drm/armada/armada_crtc.c    |  766 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/armada/armada_crtc.h    |   75 +++
 drivers/gpu/drm/armada/armada_debugfs.c |  186 ++++++++
 drivers/gpu/drm/armada/armada_drm.h     |   62 +++
 drivers/gpu/drm/armada/armada_drv.c     |  326 +++++++++++++
 drivers/gpu/drm/armada/armada_fb.c      |  156 +++++++
 drivers/gpu/drm/armada/armada_fb.h      |   21 +
 drivers/gpu/drm/armada/armada_fbdev.c   |  210 +++++++++
 drivers/gpu/drm/armada/armada_gem.c     |  420 +++++++++++++++++
 drivers/gpu/drm/armada/armada_gem.h     |   41 ++
 drivers/gpu/drm/armada/armada_hw.h      |  295 ++++++++++++
 drivers/gpu/drm/armada/armada_ioctl.h   |  128 +++++
 drivers/gpu/drm/armada/armada_ioctlP.h  |   22 +
 drivers/gpu/drm/armada/armada_output.c  |  159 +++++++
 drivers/gpu/drm/armada/armada_output.h  |   38 ++
 drivers/gpu/drm/armada/armada_overlay.c |  514 +++++++++++++++++++++
 drivers/gpu/drm/armada/armada_slave.c   |  138 ++++++
 drivers/gpu/drm/armada/armada_slave.h   |   26 +
 drivers/gpu/drm/armada/drm_helper.h     |   31 ++
 23 files changed, 3638 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/armada/Kconfig
 create mode 100644 drivers/gpu/drm/armada/Makefile
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.c
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.h
 create mode 100644 drivers/gpu/drm/armada/armada_debugfs.c
 create mode 100644 drivers/gpu/drm/armada/armada_drm.h
 create mode 100644 drivers/gpu/drm/armada/armada_drv.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.h
 create mode 100644 drivers/gpu/drm/armada/armada_fbdev.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.h
 create mode 100644 drivers/gpu/drm/armada/armada_hw.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctl.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctlP.h
 create mode 100644 drivers/gpu/drm/armada/armada_output.c
 create mode 100644 drivers/gpu/drm/armada/armada_output.h
 create mode 100644 drivers/gpu/drm/armada/armada_overlay.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.h
 create mode 100644 drivers/gpu/drm/armada/drm_helper.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 1e82882..ae8a57f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
 
 source "drivers/gpu/drm/cirrus/Kconfig"
 
+source "drivers/gpu/drm/armada/Kconfig"
+
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d59b24..b458168 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
+obj-$(CONFIG_DRM_ARMADA) += armada/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-$(CONFIG_DRM_OMAP)	+= omapdrm/
diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
new file mode 100644
index 0000000..c7a0a94
--- /dev/null
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -0,0 +1,15 @@
+config DRM_ARMADA
+	tristate "DRM support for Marvell Armada SoCs"
+	depends on DRM && HAVE_CLK
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select DRM_KMS_HELPER
+	help
+	  Support the "LCD" controllers found on the Marvell Armada 510
+	  devices.  There are two controllers on the device, each controller
+	  supports graphics and video overlays.
+
+	  This driver provides no built-in acceleration; acceleration is
+	  performed by other IP found on the SoC.  This driver provides
+	  kernel mode setting and buffer management to userspace.
diff --git a/drivers/gpu/drm/armada/Makefile b/drivers/gpu/drm/armada/Makefile
new file mode 100644
index 0000000..430f025
--- /dev/null
+++ b/drivers/gpu/drm/armada/Makefile
@@ -0,0 +1,6 @@
+armada-y	:= armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
+		   armada_gem.o armada_output.o armada_overlay.o \
+		   armada_slave.o
+armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o
+
+obj-$(CONFIG_DRM_ARMADA) := armada.o
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
new file mode 100644
index 0000000..e5ab4cb
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -0,0 +1,766 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+
+struct armada_frame_work {
+	struct drm_pending_vblank_event *event;
+	struct armada_regs regs[4];
+	struct drm_gem_object *old_fb_obj;
+	struct work_struct work;
+};
+
+/*
+ * A note about interlacing.  Let's consider HDMI 1920x1080i.
+ * The timing parameters we have from X are:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640  1080 1084 1094 1125
+ * Which get translated to:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640   540  542  547  562
+ *
+ * This is how it is defined by CEA-861-D - line and pixel numbers are
+ * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
+ * line: 2640.  The odd frame, the first active line is at line 21, and
+ * the even frame, the first active line is 584.
+ *
+ * LN:    560     561     562     563             567     568    569
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
+ *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
+ *
+ * LN:    1123   1124    1125      1               5       6      7
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
+ *  23 blanking lines
+ *
+ * The Armada LCD Controller line and pixel numbers are, like X timings,
+ * referenced to the top left of the active frame.
+ *
+ * So, translating these to our LCD controller:
+ *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
+ *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
+ * Note: Vsync front porch remains constant!
+ *
+ * if (odd_frame) {
+ *   vtotal = mode->crtc_vtotal + 1;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
+ *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
+ * } else {
+ *   vtotal = mode->crtc_vtotal;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
+ *   vhorizpos = mode->crtc_hsync_start;
+ * }
+ * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
+ *
+ * So, we need to reprogram these registers on each vsync event:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *
+ * Note: we do not use the frame done interrupts because these appear
+ * to happen too early, and lead to jitter on the display (presumably
+ * they occur at the end of the last active line, before the vsync back
+ * porch, which we're reprogramming.)
+ */
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+void armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs)
+{
+	while (regs->offset != ~0) {
+		void __iomem *reg = dcrtc->base + regs->offset;
+		uint32_t val;
+
+		val = regs->mask;
+		if (val != 0)
+			val &= readl_relaxed(reg);
+		writel_relaxed(val | regs->val, reg);
+		++regs;
+	}
+}
+
+#define dpms_blanked(dpms)	((dpms) != DRM_MODE_DPMS_ON)
+
+static void armada_drm_crtc_update(struct armada_crtc *dcrtc)
+{
+	uint32_t dumb_ctrl;
+
+	dumb_ctrl = dcrtc->cfg_dumb_ctrl;
+
+	if (!dpms_blanked(dcrtc->dpms))
+		dumb_ctrl |= CFG_DUMB_ENA;
+
+	/*
+	 * When a dumb interface isn't under 24bit, it might be
+	 * under SPI or GPIO.  If set to 7, this will force
+	 * LCD_D[23:0] to output blank color and damage GPIO
+	 * and SPI behaviour.  So leave it as-is unless in
+	 * DUMB24_RGB888_0 mode.
+	 */
+	if (dpms_blanked(dcrtc->dpms) &&
+	    (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
+		dumb_ctrl &= ~DUMB_MASK;
+		dumb_ctrl |= DUMB_BLANK;
+	}
+
+	/*
+	 * The spec is unclear about the polarities of the syncs.
+	 * We assume their non-inverted state is active high.
+	 */
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC)
+		dumb_ctrl |= CFG_INV_CSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC)
+		dumb_ctrl |= CFG_INV_HSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC)
+		dumb_ctrl |= CFG_INV_VSYNC;
+
+	if (dcrtc->dumb_ctrl != dumb_ctrl) {
+		dcrtc->dumb_ctrl = dumb_ctrl;
+		writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+}
+
+static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb, int x, int y,
+	struct armada_regs *regs, bool interlaced)
+{
+	struct armada_gem_object *obj = drm_fb_obj(fb);
+	unsigned pitch = fb->pitches[0];
+	unsigned offset = y * pitch + x * fb->bits_per_pixel / 8;
+	uint32_t addr_odd, addr_even;
+	unsigned i = 0;
+
+	DRM_DEBUG_DRIVER("pitch %u x %d y %d bpp %d\n",
+		pitch, x, y, fb->bits_per_pixel);
+
+	addr_odd = addr_even = obj->dev_addr + offset;
+
+	if (interlaced) {
+		addr_even += pitch;
+		pitch *= 2;
+	}
+
+	/* write offset, base, and pitch */
+	armada_reg_queue_set(regs, i, addr_odd, LCD_CFG_GRA_START_ADDR0);
+	armada_reg_queue_set(regs, i, addr_even, LCD_CFG_GRA_START_ADDR1);
+	armada_reg_queue_mod(regs, i, pitch, 0xffff, LCD_CFG_GRA_PITCH);
+
+	return i;
+}
+
+static void armada_drm_unref_work(struct work_struct *_work)
+{
+	struct armada_frame_work *work =
+		container_of(_work, struct armada_frame_work, work);
+	drm_gem_object_unreference_unlocked(work->old_fb_obj);
+	kfree(work);
+}
+
+static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
+	struct armada_frame_work *work)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	unsigned long flags;
+	int ret;
+
+	INIT_WORK(&work->work, armada_drm_unref_work);
+
+	ret = drm_vblank_get(dev, dcrtc->num);
+	if (ret) {
+		DRM_ERROR("failed to acquire vblank counter\n");
+		return ret;
+	}
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (!dcrtc->frame_work) {
+		dcrtc->frame_work = work;
+	} else {
+		ret = -EBUSY;
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+
+	if (ret)
+		drm_vblank_put(dev, dcrtc->num);
+
+	return ret;
+}
+
+static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	struct armada_frame_work *work = dcrtc->frame_work;
+
+	dcrtc->frame_work = NULL;
+
+	armada_drm_crtc_update_regs(dcrtc, work->regs);
+
+	if (work->event)
+		drm_send_vblank_event(dev, dcrtc->num, work->event);
+
+	drm_vblank_put(dev, dcrtc->num);
+
+	/* Finally, queue the process-half of the cleanup */
+	schedule_work(&work->work);
+}
+
+static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
+	struct drm_framebuffer *fb, bool force)
+{
+	struct armada_gem_object *obj;
+	struct armada_frame_work *work;
+
+	if (!fb)
+		return;
+
+	obj = drm_fb_obj(fb);
+	if (force) {
+		/* Display is disabled, so just drop the old fb */
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return;
+	}
+
+	work = kmalloc(sizeof *work, GFP_KERNEL);
+	if (work) {
+		int i = 0;
+		work->event = NULL;
+		work->old_fb_obj = &obj->obj;
+		armada_reg_queue_end(work->regs, i);
+
+		if (armada_drm_crtc_queue_frame_work(dcrtc, work) == 0)
+			return;
+
+		kfree(work);
+	}
+
+	/*
+	 * Oops - just drop the reference immediately and hope for
+	 * the best.  The worst that will happen is the buffer gets
+	 * reused before it has finished being displayed.
+	 */
+	drm_gem_object_unreference_unlocked(&obj->obj);
+}
+
+static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+
+	/*
+	 * Tell the DRM core that vblank IRQs aren't going to happen for
+	 * a while.  This cleans up any pending vblank events for us.
+	 */
+	drm_vblank_off(dev, dcrtc->num);
+
+	/* Handle any pending flip event. */
+	spin_lock_irq(&dev->event_lock);
+	if (dcrtc->frame_work)
+		armada_drm_crtc_complete_frame_work(dcrtc);
+	spin_unlock_irq(&dev->event_lock);
+}
+
+void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b, int idx)
+{
+}
+
+void armada_drm_crtc_gamma_get(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, int idx)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_dpms(struct drm_crtc *crtc, int dpms)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != dpms) {
+		dcrtc->dpms = dpms;
+		armada_drm_crtc_update(dcrtc);
+		if (dpms_blanked(dpms))
+			armada_drm_vblank_off(dcrtc);
+	}
+}
+
+/*
+ * Prepare for a mode set.  Turn off overlay to ensure that we don't end
+ * up with the overlay size being bigger than the active screen size.
+ * We rely upon X refreshing this state after the mode set has completed.
+ *
+ * The mode_config.mutex will be held for this call
+ */
+static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	mutex_lock(&dev->struct_mutex);
+	if (dcrtc->overlay)
+		armada_drm_overlay_off(dev, dcrtc->overlay);
+	mutex_unlock(&dev->struct_mutex);
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_commit(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_ON) {
+		dcrtc->dpms = DRM_MODE_DPMS_ON;
+		armada_drm_crtc_update(dcrtc);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj)
+{
+	return true;
+}
+
+void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
+{
+	struct armada_vbl_event *e, *n;
+	void __iomem *base = dcrtc->base;
+
+	if (stat & DMA_FF_UNDERFLOW)
+		DRM_ERROR("video underflow on crtc %u\n", dcrtc->num);
+	if (stat & GRA_FF_UNDERFLOW)
+		DRM_ERROR("graphics underflow on crtc %u\n", dcrtc->num);
+
+	if (stat & VSYNC_IRQ)
+		drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num);
+
+	spin_lock(&dcrtc->irq_lock);
+
+	list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) {
+		list_del_init(&e->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		e->fn(dcrtc, e->data);
+	}
+
+	if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) {
+		int i = stat & GRA_FRAME_IRQ0 ? 0 : 1;
+		uint32_t val;
+
+		writel_relaxed(dcrtc->v[i].spu_v_porch, base + LCD_SPU_V_PORCH);
+		writel_relaxed(dcrtc->v[i].spu_v_h_total, base + LCD_SPUT_V_H_TOTAL);
+
+		val = readl_relaxed(base + LCD_SPU_ADV_REG);
+		val &= ~(0xfff << 20 | 0xfff);
+		val |= dcrtc->v[i].spu_adv_reg;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG);
+	}
+	spin_unlock(&dcrtc->irq_lock);
+
+	/* Only on frame 0 IRQs (start of progressive / odd frame) */
+	if (stat & GRA_FRAME_IRQ0) {
+		struct drm_device *dev = dcrtc->crtc.dev;
+
+		spin_lock(&dev->event_lock);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock(&dev->event_lock);
+
+		wake_up(&dcrtc->frame_wait);
+	}
+}
+
+/* These are locked by dev->vbl_lock */
+void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if (dcrtc->irq_ena & mask) {
+		dcrtc->irq_ena &= ~mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	}
+}
+
+void armada_drm_crtc_enable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if ((dcrtc->irq_ena & mask) != mask) {
+		dcrtc->irq_ena |= mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+		if (readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR) & mask)
+			writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
+	struct drm_display_mode *mode, struct drm_display_mode *adj,
+	int x, int y, struct drm_framebuffer *old_fb)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[16];
+	uint32_t lm, rm, tm, bm, val, rate, ref, div;
+	unsigned long flags;
+	unsigned i;
+	bool interlaced;
+
+	drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
+
+	interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
+
+	i = armada_drm_crtc_calc_fb(dcrtc->crtc.fb, x, y, regs, interlaced);
+
+	rm = adj->crtc_hsync_start - adj->crtc_hdisplay;
+	lm = adj->crtc_htotal - adj->crtc_hsync_end;
+	bm = adj->crtc_vsync_start - adj->crtc_vdisplay;
+	tm = adj->crtc_vtotal - adj->crtc_vsync_end;
+
+	DRM_DEBUG_DRIVER("H: %d %d %d %d lm %d rm %d\n",
+		adj->crtc_hdisplay,
+		adj->crtc_hsync_start,
+		adj->crtc_hsync_end,
+		adj->crtc_htotal, lm, rm);
+	DRM_DEBUG_DRIVER("V: %d %d %d %d tm %d bm %d\n",
+		adj->crtc_vdisplay,
+		adj->crtc_vsync_start,
+		adj->crtc_vsync_end,
+		adj->crtc_vtotal, tm, bm);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	drm_vblank_pre_modeset(crtc->dev, dcrtc->num);
+
+	crtc->mode = *adj;
+
+	val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA;
+	if (val != dcrtc->dumb_ctrl) {
+		dcrtc->dumb_ctrl = val;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+
+	rate = adj->clock * 1000;
+	clk_set_rate(dcrtc->clk, rate);
+	ref = clk_get_rate(dcrtc->clk);
+	div = DIV_ROUND_UP(ref, rate);
+	if (div < 1)
+		div = 1;
+	armada_reg_queue_set(regs, i, div | dcrtc->cfg_sclk, LCD_CFG_SCLK_DIV);
+
+	if (interlaced ^ dcrtc->interlaced) {
+		if (adj->flags & DRM_MODE_FLAG_INTERLACE)
+			drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+		else
+			drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		dcrtc->interlaced = interlaced;
+	}
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+
+	/* Even interlaced/progressive frame */
+	dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 |
+				    adj->crtc_htotal;
+	dcrtc->v[1].spu_v_porch = tm << 16 | bm;
+	val = adj->crtc_hsync_start;
+	dcrtc->v[1].spu_adv_reg = val << 20 | val;
+
+	if (interlaced) {
+		/* Odd interlaced frame */
+		dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total +
+						(1 << 16);
+		dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1;
+		val = adj->crtc_hsync_start - adj->crtc_htotal / 2;
+		dcrtc->v[0].spu_adv_reg = val << 20 | val;
+	} else {
+		dcrtc->v[0] = dcrtc->v[1];
+	}
+
+	val = (adj->crtc_vdisplay << 16) | adj->crtc_hdisplay;
+
+	armada_reg_queue_set(regs, i, val, LCD_SPU_V_H_ACTIVE);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GRA_HPXL_VLN);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GZM_HPXL_VLN);
+	armada_reg_queue_set(regs, i, (lm << 16) | rm, LCD_SPU_H_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_porch, LCD_SPU_V_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total,
+			   LCD_SPUT_V_H_TOTAL);
+	armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg | ADV_VSYNCOFFEN,
+			   (0xfff << 20 | 0xfff), LCD_SPU_ADV_REG);
+
+	val = dcrtc->cfg_dma_ctrl0;
+
+	switch (crtc->fb->pixel_format) {
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+		val |= CFG_GRA_1555;
+		break;
+	case DRM_FORMAT_RGB565:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR565:
+		val |= CFG_GRA_565;
+		break;
+	case DRM_FORMAT_RGB888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR888:
+		val |= CFG_GRA_888PACK;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR8888:
+		val |= CFG_GRA_X888;
+		break;
+	case DRM_FORMAT_ARGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_ABGR8888:
+		val |= CFG_GRA_8888;
+		break;
+	case DRM_FORMAT_C8:
+		val |= CFG_GRA_PSEUDO8 | CFG_PALETTE_ENA;
+		break;
+	}
+	if (interlaced)
+		val |= CFG_GRA_FTOGGLE;
+	armada_reg_queue_mod(regs, i, val, CFG_GRAFORMAT | CFG_GRA_SWAPRB |
+			   CFG_PALETTE_ENA | CFG_GRA_FTOGGLE,
+			   LCD_SPU_DMA_CTRL0);
+
+	val = adj->flags & DRM_MODE_FLAG_NVSYNC ? CFG_VSYNC_INV : 0;
+	armada_reg_queue_mod(regs, i, val, CFG_VSYNC_INV, LCD_SPU_DMA_CTRL1);
+
+	/*
+	 * Set the colorimetry, based upon the HDMI spec.
+	 * 1280x720p, 1920x1080p and 1920x1080i use ITU709, others
+	 * use ITU601.  In any case, we select professional.
+	 */
+	if ((adj->hdisplay == 1280 && adj->vdisplay == 720 && !interlaced) ||
+	    (adj->hdisplay == 1920 && adj->vdisplay == 1080)) {
+		val = CFG_CSC_PROF | CFG_CSC_CCIR709;
+	} else {
+		val = CFG_CSC_PROF;
+	}
+	armada_reg_queue_mod(regs, i, val, CFG_CSC_MASK, LCD_SPU_IOPAD_CONTROL);
+	armada_reg_queue_end(regs, i);
+
+	armada_drm_crtc_update_regs(dcrtc, regs);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+
+	armada_drm_crtc_update(dcrtc);
+
+	drm_vblank_post_modeset(crtc->dev, dcrtc->num);
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+	struct drm_framebuffer *old_fb)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[4];
+	unsigned i;
+
+	i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
+	armada_reg_queue_end(regs, i);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	/* Take a reference to the new fb as we're using it */
+	drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
+
+	/* Update the base in the CRTC */
+	armada_drm_crtc_update_regs(dcrtc, regs);
+
+	/* Drop our previously held reference */
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+static void armada_drm_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_disable(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_OFF) {
+		dcrtc->dpms = DRM_MODE_DPMS_OFF;
+		armada_drm_crtc_update(dcrtc);
+	}
+	armada_drm_vblank_off(dcrtc);
+	armada_drm_crtc_finish_fb(dcrtc, crtc->fb, true);
+}
+
+static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
+	.dpms		= armada_drm_crtc_dpms,
+	.prepare	= armada_drm_crtc_prepare,
+	.commit		= armada_drm_crtc_commit,
+	.mode_fixup	= armada_drm_crtc_mode_fixup,
+	.mode_set	= armada_drm_crtc_mode_set,
+	.mode_set_base	= armada_drm_crtc_mode_set_base,
+	.load_lut	= armada_drm_crtc_load_lut,
+	.disable	= armada_drm_crtc_disable,
+};
+
+static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_private *priv = crtc->dev->dev_private;
+
+	priv->dcrtc[dcrtc->num] = NULL;
+	drm_crtc_cleanup(&dcrtc->crtc);
+	clk_disable_unprepare(dcrtc->clk);
+	clk_put(dcrtc->clk);
+	kfree(dcrtc);
+}
+
+/*
+ * The mode_config lock is held here, to prevent races between this
+ * and a mode_set.
+ */
+static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
+	struct drm_framebuffer *fb, struct drm_pending_vblank_event *event)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_frame_work *work;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+	unsigned i;
+	int ret;
+
+	/* We don't support changing the pixel format */
+	if (fb->pixel_format != crtc->fb->pixel_format)
+		return -EINVAL;
+
+	work = kmalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return -ENOMEM;
+
+	work->event = event;
+	work->old_fb_obj = &drm_fb_obj(dcrtc->crtc.fb)->obj;
+
+	i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs, dcrtc->interlaced);
+	armada_reg_queue_end(work->regs, i);
+
+	/*
+	 * Hold the old framebuffer for the work - DRM appears to drop our
+	 * reference to the old framebuffer in drm_mode_page_flip_ioctl().
+	 */
+	drm_gem_object_reference(work->old_fb_obj);
+
+	ret = armada_drm_crtc_queue_frame_work(dcrtc, work);
+	if (ret) {
+		/*
+		 * Undo our reference above; DRM does not drop the reference
+		 * to this object on error, so that's okay.
+		 */
+		drm_gem_object_unreference(work->old_fb_obj);
+		kfree(work);
+		return ret;
+	}
+
+	/*
+	 * Don't take a reference on the new framebuffer;
+	 * drm_mode_page_flip_ioctl() has already grabbed a reference and
+	 * will _not_ drop that reference on successful return from this
+	 * function.  Simply mark this new framebuffer as the current one.
+	 */
+	dcrtc->crtc.fb = fb;
+
+	/*
+	 * Finally, if the display is blanked, we won't receive an
+	 * interrupt, so complete it now.
+	 */
+	if (dpms_blanked(dcrtc->dpms)) {
+		spin_lock_irqsave(&dev->event_lock, flags);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+
+	return 0;
+}
+
+static struct drm_crtc_funcs armada_crtc_funcs = {
+	.destroy	= armada_drm_crtc_destroy,
+	.page_flip	= armada_drm_crtc_page_flip,
+	.set_config	= drm_crtc_helper_set_config,
+};
+
+int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
+	struct resource *res, struct clk *clk)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc;
+	void __iomem *base;
+	int ret;
+
+	ret = clk_prepare_enable(clk);
+	if (ret) {
+		DRM_ERROR("clk would not prepare and enable\n");
+		return ret;
+	}
+
+	base = devm_request_and_ioremap(dev->dev, res);
+	if (!base) {
+		DRM_ERROR("failed to ioremap register\n");
+		clk_disable_unprepare(clk);
+		return -ENOMEM;
+	}
+
+	dcrtc = kzalloc(sizeof(*dcrtc), GFP_KERNEL);
+	if (!dcrtc) {
+		DRM_ERROR("failed to allocate Armada crtc\n");
+		clk_disable_unprepare(clk);
+		return -ENOMEM;
+	}
+
+	dcrtc->base = base;
+	dcrtc->num = num;
+	dcrtc->clk = clk;
+	dcrtc->cfg_sclk = 0xc0000000;
+	dcrtc->cfg_dma_ctrl0 = CFG_GRA_ENA | CFG_GRA_HSMOOTH;
+	dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0;
+	spin_lock_init(&dcrtc->irq_lock);
+	dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR;
+	INIT_LIST_HEAD(&dcrtc->vbl_list);
+	init_waitqueue_head(&dcrtc->frame_wait);
+
+	/* Initialize some registers which we don't otherwise set */
+	writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_BLANKCOLOR);
+	writel_relaxed(CFG_IOPAD_DUMB24, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_SRAM_PARA0);
+	writel_relaxed(0x0000e000, dcrtc->base + LCD_SPU_SRAM_PARA1);
+	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
+
+	/* Lower the watermark so to eliminate jitter at higher bandwidths */
+	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
+
+	/* Ensure AXI pipeline is enabled */
+	armada_updatel(CFG_ARBFAST_ENA, 0, dcrtc->base + LCD_SPU_DMA_CTRL0);
+
+	priv->dcrtc[dcrtc->num] = dcrtc;
+
+	drm_crtc_init(dev, &dcrtc->crtc, &armada_crtc_funcs);
+	drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
new file mode 100644
index 0000000..53d1efe
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CRTC_H
+#define ARMADA_CRTC_H
+
+struct armada_overlay;
+struct armada_gem_object;
+
+struct armada_regs {
+	uint32_t offset;
+	uint32_t mask;
+	uint32_t val;
+};
+
+#define armada_reg_queue_mod(_r, _i, _v, _m, _o)	\
+	do {					\
+		struct armada_regs *__reg = _r;	\
+		__reg[_i].offset = _o;		\
+		__reg[_i].mask = ~(_m);		\
+		__reg[_i].val = _v;		\
+		_i++;				\
+	} while (0)
+
+#define armada_reg_queue_set(_r, _i, _v, _o)	\
+	armada_reg_queue_mod(_r, _i, _v, ~0, _o)
+
+#define armada_reg_queue_end(_r, _i)		\
+	armada_reg_queue_mod(_r, _i, 0, 0, ~0)
+
+struct armada_frame_work;
+
+struct armada_crtc {
+	struct drm_crtc		crtc;
+	unsigned		num;
+	void __iomem		*base;
+	struct clk		*clk;
+	struct {
+		uint32_t	spu_v_h_total;
+		uint32_t	spu_v_porch;
+		uint32_t	spu_adv_reg;
+	} v[2];
+	bool			interlaced;
+
+	struct armada_overlay	*overlay;
+
+	int			dpms;
+	uint32_t		cfg_sclk;
+	uint32_t		cfg_dma_ctrl0;
+	uint32_t		cfg_dumb_ctrl;
+	uint32_t		dumb_ctrl;
+
+	wait_queue_head_t	frame_wait;
+	struct armada_frame_work	*frame_work;
+
+	spinlock_t		irq_lock;
+	uint32_t		irq_ena;
+	struct list_head	vbl_list;
+};
+#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)
+
+int armada_drm_crtc_create(struct drm_device *, unsigned, struct resource *,
+	struct clk *);
+void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
+void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
+void armada_drm_crtc_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_debugfs.c b/drivers/gpu/drm/armada/armada_debugfs.c
new file mode 100644
index 0000000..ff5ada3
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_debugfs.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <drm/drmP.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+
+static int armada_debugfs_gem_offs_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct drm_gem_mm *mm = dev->mm_private;
+
+	return drm_mm_dump_table(m, &mm->offset_manager);
+}
+
+static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct armada_private *priv = node->minor->dev->dev_private;
+
+	return drm_mm_dump_table(m, &priv->linear);
+}
+
+static int armada_debugfs_reg_show(struct seq_file *m, void *data)
+{
+	struct drm_device *dev = m->private;
+	struct armada_private *priv = dev->dev_private;
+	int n, i;
+
+	if (priv) {
+		for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+			struct armada_crtc *dcrtc = priv->dcrtc[n];
+			if (!dcrtc)
+				continue;
+
+			for (i = 0x84; i <= 0x1c4; i += 4) {
+				uint32_t v = readl_relaxed(dcrtc->base + i);
+				seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, armada_debugfs_reg_show, inode->i_private);
+}
+
+static const struct file_operations fops_reg_r = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_r_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int armada_debugfs_write(struct file *file, const char __user *ptr,
+	size_t len, loff_t *off)
+{
+	struct drm_device *dev = file->private_data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	char buf[32], *p;
+	uint32_t reg, val;
+	int ret;
+
+	if (*off != 0)
+		return 0;
+
+	if (len > sizeof(buf) - 1)
+		len = sizeof(buf) - 1;
+
+	ret = strncpy_from_user(buf, ptr, len);
+	if (ret < 0)
+		return ret;
+	buf[len] = '\0';
+
+	reg = simple_strtoul(buf, &p, 16);
+	if (!isspace(*p))
+		return -EINVAL;
+	val = simple_strtoul(p + 1, NULL, 16);
+
+	if (reg >= 0x84 && reg <= 0x1c4)
+		writel(val, dcrtc->base + reg);
+
+	return len;
+}
+
+static int armada_debugfs_reg_w_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static const struct file_operations fops_reg_w = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_w_open,
+	.write = armada_debugfs_write,
+	.llseek = noop_llseek,
+};
+
+static struct drm_info_list armada_debugfs_list[] = {
+	{ "gem_linear", armada_debugfs_gem_linear_show, 0 },
+	{ "gem_offset", armada_debugfs_gem_offs_show, 0 },
+};
+#define ARMADA_DEBUGFS_ENTRIES ARRAY_SIZE(armada_debugfs_list)
+
+static int drm_add_fake_info_node(struct drm_minor *minor, struct dentry *ent,
+	const void *key)
+{
+	struct drm_info_node *node;
+
+	node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL);
+	if (node == NULL) {
+		debugfs_remove(ent);
+		return -ENOMEM;
+	}
+
+	node->minor = minor;
+	node->dent = ent;
+	node->info_ent = (void *) key;
+
+	mutex_lock(&minor->debugfs_lock);
+	list_add(&node->list, &minor->debugfs_list);
+	mutex_unlock(&minor->debugfs_lock);
+
+	return 0;
+}
+
+static int armada_debugfs_create(struct dentry *root, struct drm_minor *minor,
+	const char *name, umode_t mode, const struct file_operations *fops)
+{
+	struct dentry *de;
+
+	de = debugfs_create_file(name, mode, root, minor->dev, fops);
+
+	return drm_add_fake_info_node(minor, de, fops);
+}
+
+int armada_drm_debugfs_init(struct drm_minor *minor)
+{
+	int ret;
+
+	ret = drm_debugfs_create_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				       minor->debugfs_root, minor);
+	if (ret)
+		return ret;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				   "reg", S_IFREG | S_IRUSR, &fops_reg_r);
+	if (ret)
+		goto err_1;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				"reg_wr", S_IFREG | S_IWUSR, &fops_reg_w);
+	if (ret)
+		goto err_2;
+	return ret;
+
+ err_2:
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+ err_1:
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+	return ret;
+}
+
+void armada_drm_debugfs_cleanup(struct drm_minor *minor)
+{
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_w, 1, minor);
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+}
diff --git a/drivers/gpu/drm/armada/armada_drm.h b/drivers/gpu/drm/armada/armada_drm.h
new file mode 100644
index 0000000..c73e29b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drm.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_DRM_H
+#define ARMADA_DRM_H
+
+struct armada_crtc;
+struct armada_gem_object;
+struct armada_overlay;
+struct clk;
+
+static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp)
+{
+	uint32_t pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2;
+
+	/* 88AP510 spec recommends pitch be a multiple of 128 */
+	return ALIGN(pitch, 128);
+}
+
+struct armada_vbl_event {
+	struct list_head	node;
+	void			*data;
+	void			(*fn)(struct armada_crtc *, void *);
+};
+void armada_drm_vbl_event_add(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *, struct armada_vbl_event *);
+#define armada_drm_vbl_event_init(_e,_f,_d) do {	\
+	struct armada_vbl_event *__e = _e;	\
+	INIT_LIST_HEAD(&__e->node);		\
+	__e->data = _d;				\
+	__e->fn = _f;				\
+} while (0)
+
+
+struct armada_private {
+	struct drm_fb_helper	*fbdev;
+	struct armada_crtc	*dcrtc[2];
+	struct armada_overlay	*overlay;
+	struct drm_mm		linear;
+#ifdef CONFIG_DEBUG_FS
+	struct dentry		*de;
+#endif
+};
+
+extern const struct drm_mode_config_funcs armada_drm_mode_config_funcs;
+
+int armada_fbdev_init(struct drm_device *);
+void armada_fbdev_fini(struct drm_device *);
+
+int armada_overlay_create(struct drm_device *);
+void armada_overlay_destroy(struct drm_device *);
+void armada_drm_overlay_off(struct drm_device *, struct armada_overlay *);
+
+int armada_drm_debugfs_init(struct drm_minor *);
+void armada_drm_debugfs_cleanup(struct drm_minor *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
new file mode 100644
index 0000000..bb70cf5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+static int armada_drm_load(struct drm_device *dev, unsigned long flags)
+{
+	struct armada_private *priv;
+	struct resource *res[ARRAY_SIZE(priv->dcrtc)];
+	struct resource *mem = NULL;
+	int ret, n, i;
+
+	memset(res, 0, sizeof(res));
+
+	for (n = i = 0; ; n++) {
+		struct resource *r = platform_get_resource(dev->platformdev,
+							   IORESOURCE_MEM, n);
+		if (!r)
+			break;
+
+		if (resource_size(r) > SZ_4K)
+			mem = r;
+		else if (i < ARRAY_SIZE(priv->dcrtc))
+			res[i++] = r;
+		else
+			return -EINVAL;
+	}
+
+	if (!res[0] || !mem)
+		return -ENXIO;
+
+	if (!devm_request_mem_region(dev->dev, mem->start,
+			resource_size(mem), "armada-drm"))
+		return -EBUSY;
+
+	priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		DRM_ERROR("failed to allocate private\n");
+		ret = -ENOMEM;
+		goto err_buf;
+	}
+
+	dev->dev_private = priv;
+
+	/* Mode setting support */
+	drm_mode_config_init(dev);
+	dev->mode_config.min_width = 0;
+	dev->mode_config.min_height = 0;
+	dev->mode_config.max_width = 2048;
+	dev->mode_config.max_height = 2048;
+	dev->mode_config.preferred_depth = 24;
+	dev->mode_config.funcs = &armada_drm_mode_config_funcs;
+	drm_mm_init(&priv->linear, mem->start, resource_size(mem));
+
+	/* Create all LCD controllers */
+	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+		struct clk *clk;
+
+		if (!res[n])
+			break;
+
+		clk = clk_get_sys("dovefb.0", "extclk");
+		if (IS_ERR(clk)) {
+			DRM_ERROR("failed to get clock\n");
+			ret = PTR_ERR(clk);
+			if (ret == -ENOENT)
+				ret = -EPROBE_DEFER;
+			goto err_kms;
+		}
+
+		ret = armada_drm_crtc_create(dev, n, res[n], clk);
+		if (ret) {
+			clk_put(clk);
+			goto err_kms;
+		}
+	}
+
+	ret = drm_vblank_init(dev, n);
+	if (ret)
+		goto err_kms;
+
+	ret = drm_irq_install(dev);
+	if (ret)
+		goto err_kms;
+
+	dev->vblank_disable_allowed = 1;
+
+	ret = armada_fbdev_init(dev);
+	if (ret)
+		goto err_irq;
+
+	ret = armada_overlay_create(dev);
+	if (ret)
+		goto err_irq;
+
+	drm_kms_helper_poll_init(dev);
+
+	return 0;
+
+ err_irq:
+	drm_irq_uninstall(dev);
+ err_kms:
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+ err_buf:
+
+	return ret;
+}
+
+static int armada_drm_unload(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+
+	drm_kms_helper_poll_fini(dev);
+	armada_overlay_destroy(dev);
+	armada_fbdev_fini(dev);
+	drm_irq_uninstall(dev);
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+	dev->dev_private = NULL;
+
+	return 0;
+}
+
+void armada_drm_vbl_event_add(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	if (list_empty(&evt->node)) {
+		list_add_tail(&evt->node, &dcrtc->vbl_list);
+
+		drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+	}
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+void armada_drm_vbl_event_remove(struct armada_crtc *dcrtc,
+	struct armada_vbl_event *evt)
+{
+	if (!list_empty(&evt->node)) {
+		list_del_init(&evt->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+	}
+}
+
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	armada_drm_vbl_event_remove(dcrtc, evt);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+/* These are called under the vbl_lock. */
+static int armada_drm_enable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_enable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+	return 0;
+}
+
+static void armada_drm_disable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_disable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+}
+
+static irqreturn_t armada_drm_irq_handler(int irq, void *arg)
+{
+	struct drm_device *dev = arg;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	uint32_t v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);
+	irqreturn_t handled = IRQ_NONE;
+
+	/*
+	 * This is rediculous - rather than writing bits to clear, we
+	 * have to set the actual status register value.  This is racy.
+	 */
+	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+
+	/* Mask out those interrupts we haven't enabled */
+	v = stat & dcrtc->irq_ena;
+
+	if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) {
+		armada_drm_crtc_irq(dcrtc, stat);
+		handled = IRQ_HANDLED;
+	}
+
+	return handled;
+}
+
+static int armada_drm_irq_postinstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	spin_lock_irq(&dev->vbl_lock);
+	writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	spin_unlock_irq(&dev->vbl_lock);
+
+	return 0;
+}
+
+static void armada_drm_irq_uninstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ENA);
+}
+
+static struct drm_ioctl_desc armada_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_MMAP, armada_gem_mmap_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PROP, armada_gem_prop_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PWRITE, armada_gem_pwrite_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_PUT_IMAGE, armada_overlay_put_image_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_ATTRS, armada_overlay_attrs_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+};
+
+static const struct file_operations armada_drm_fops = {
+	.owner			= THIS_MODULE,
+	.llseek			= no_llseek,
+	.read			= drm_read,
+	.poll			= drm_poll,
+	.unlocked_ioctl		= drm_ioctl,
+	.mmap			= drm_gem_mmap,
+	.open			= drm_open,
+	.release		= drm_release,
+	.fasync			= drm_fasync,
+};
+
+extern const struct vm_operations_struct armada_gem_vm_ops;
+
+static struct drm_driver armada_drm_driver = {
+	.load			= armada_drm_load,
+	.open			= NULL,
+	.preclose		= NULL,
+	.postclose		= NULL,
+	.lastclose		= NULL,
+	.unload			= armada_drm_unload,
+	.get_vblank_counter	= drm_vblank_count,
+	.enable_vblank		= armada_drm_enable_vblank,
+	.disable_vblank		= armada_drm_disable_vblank,
+	.irq_handler		= armada_drm_irq_handler,
+	.irq_postinstall	= armada_drm_irq_postinstall,
+	.irq_uninstall		= armada_drm_irq_uninstall,
+#ifdef CONFIG_DEBUG_FS
+	.debugfs_init		= armada_drm_debugfs_init,
+	.debugfs_cleanup	= armada_drm_debugfs_cleanup,
+#endif
+	.gem_init_object	= NULL,
+	.gem_free_object	= armada_gem_free_object,
+	.prime_handle_to_fd	= NULL,
+	.prime_fd_to_handle	= NULL,
+	.gem_prime_export	= NULL,
+	.gem_prime_import	= NULL,
+	.dumb_create		= armada_gem_dumb_create,
+	.dumb_map_offset	= armada_gem_dumb_map_offset,
+	.dumb_destroy		= armada_gem_dumb_destroy,
+	.gem_vm_ops		= &armada_gem_vm_ops,
+	.major			= 1,
+	.minor			= 0,
+	.name			= "armada-drm",
+	.desc			= "Armada SoC DRM",
+	.date			= "20120730",
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_HAVE_IRQ,
+	.ioctls			= armada_ioctls,
+	.fops			= &armada_drm_fops,
+};
+
+static int armada_drm_probe(struct platform_device *pdev)
+{
+	return drm_platform_init(&armada_drm_driver, pdev);
+}
+
+static int armada_drm_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&armada_drm_driver, pdev);
+	return 0;
+}
+
+static struct platform_driver armada_drm_platform_driver = {
+	.probe	= armada_drm_probe,
+	.remove	= armada_drm_remove,
+	.driver	= {
+		.owner	= THIS_MODULE,
+		.name	= "armada-drm",
+	},
+};
+
+static int __init armada_drm_init(void)
+{
+	armada_drm_driver.num_ioctls = DRM_ARRAY_SIZE(armada_ioctls);
+	return platform_driver_register(&armada_drm_platform_driver);
+}
+module_init(armada_drm_init);
+
+static void __exit armada_drm_exit(void)
+{
+	platform_driver_unregister(&armada_drm_platform_driver);
+}
+module_exit(armada_drm_exit);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Armada DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:armada-drm");
diff --git a/drivers/gpu/drm/armada/armada_fb.c b/drivers/gpu/drm/armada/armada_fb.c
new file mode 100644
index 0000000..8f72f13
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static void armada_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+
+	drm_framebuffer_cleanup(&dfb->fb);
+	if (dfb->obj)
+		drm_gem_object_unreference_unlocked(&dfb->obj->obj);
+	kfree(dfb);
+}
+
+static int armada_fb_create_handle(struct drm_framebuffer *fb,
+	struct drm_file *dfile, unsigned int *handle)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+	return drm_gem_handle_create(dfile, &dfb->obj->obj, handle);
+}
+
+static const struct drm_framebuffer_funcs armada_fb_funcs = {
+	.destroy	= armada_fb_destroy,
+	.create_handle	= armada_fb_create_handle,
+};
+
+/*
+ * Supported pixel formats:
+ *		T	R	G	B	BPP
+ * RGB565 		[15:11] [10:5]	[4:0]	16	DRM_FORMAT_RGB565
+ * BGR565		[4:0]	[10:5]	[15:11]	16	DRM_FORMAT_BGR565
+ * RGB1555	15	[14:10]	[9:5]	[4:0]	16	DRM_FORMAT_ARGB1555
+ * BGR1555	15	[4:0]	[9:5]	[14:10]	16	DRM_FORMAT_ABGR1555
+ * RGB888PACK		[23:16]	[15:8]	[7:0]	24	DRM_FORMAT_RGB888
+ * BGR888PACK		[7:0]	[15:8]	[23:16]	24	DRM_FORMAT_BGR888
+ * RGB888UNPACK		[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_XRGB8888
+ * BGR888UNPACK		[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_XBGR8888
+ * RGBA888	[31:24]	[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_ARGB8888
+ * BGRA888	[31:24]	[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_ABGR8888
+ *
+ * We don't currently support (note that the YUV pixel fields
+ * are incorrect, coming from the dovefb driver):
+ *			Y	U	V	BPP
+ * YUV422PACK		[15:8]	[7:4]	[3:0]	16
+ * YVU422PACK		[7:0]	[11:8]	[15:12]	16
+ * YUV422PLANAR		[15:8]	[7:4]	[3:0]	16
+ * YVU422PLANAR		[7:0]	[11:8]	[15:12]	16
+ * YUV420PLANAR		[11:4]	[3:2]	[1:0]	12
+ * YVU420PLANAR		[7:0]	[9:8]	[11:10]	12
+ * PSEUDOCOLOR					8
+ * UYVY422PACK		[11:4]	[15:12]	[3:0]	16
+ */
+int armada_framebuffer_create(struct drm_device *dev,
+	struct armada_framebuffer **dfbp, struct drm_mode_fb_cmd2 *mode,
+	struct armada_gem_object *obj)
+{
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	switch (mode->pixel_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
+	if (!dfb) {
+		DRM_ERROR("failed to allocate Armada fb object\n");
+		return -ENOMEM;
+	}
+
+	ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs);
+	if (ret) {
+		kfree(dfb);
+		return ret;
+	}
+
+	drm_helper_mode_fill_fb_struct(&dfb->fb, mode);
+
+	/*
+	 * Take a reference on our object - the caller is expected
+	 * to drop their reference to it.
+	 */
+	drm_gem_object_reference(&obj->obj);
+	dfb->obj = obj;
+	*dfbp = dfb;
+
+	return 0;
+}
+
+static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
+	struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode)
+{
+	struct armada_gem_object *obj;
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n",
+		mode->width, mode->height, mode->pixel_format,
+		mode->flags, mode->pitches[0], mode->pitches[1],
+		mode->pitches[2]);
+
+	/* We can only handle a single plane at the moment */
+	if (drm_format_num_planes(mode->pixel_format) > 1)
+		return ERR_PTR(-EINVAL);
+
+	obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		return ERR_PTR(-ENOENT);
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, mode, obj);
+	drm_gem_object_unreference_unlocked(&obj->obj);
+
+	if (ret) {
+		DRM_ERROR("failed to initialize framebuffer\n");
+		return ERR_PTR(ret);
+	}
+
+	return &dfb->fb;
+}
+
+static void armada_output_poll_changed(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh)
+		drm_fb_helper_hotplug_event(fbh);
+}
+
+const struct drm_mode_config_funcs armada_drm_mode_config_funcs = {
+	.fb_create		= armada_fb_create,
+	.output_poll_changed	= armada_output_poll_changed,
+};
diff --git a/drivers/gpu/drm/armada/armada_fb.h b/drivers/gpu/drm/armada/armada_fb.h
new file mode 100644
index 0000000..0c78d2b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_FB_H
+#define ARMADA_FB_H
+
+struct armada_framebuffer {
+	struct drm_framebuffer	fb;
+	struct armada_gem_object	*obj;
+};
+#define drm_fb_to_armada_fb(dfb) container_of(dfb, struct armada_framebuffer, fb)
+#define drm_fb_obj(fb) drm_fb_to_armada_fb(fb)->obj
+
+int armada_framebuffer_create(struct drm_device *, struct armada_framebuffer **,
+	struct drm_mode_fb_cmd2 *, struct armada_gem_object *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_fbdev.c b/drivers/gpu/drm/armada/armada_fbdev.c
new file mode 100644
index 0000000..001d2ec
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fbdev.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Written from the i915 driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static /*const*/ struct fb_ops armada_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_check_var	= drm_fb_helper_check_var,
+	.fb_set_par	= drm_fb_helper_set_par,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_pan_display	= drm_fb_helper_pan_display,
+	.fb_blank	= drm_fb_helper_blank,
+	.fb_setcmap	= drm_fb_helper_setcmap,
+	.fb_debug_enter	= drm_fb_helper_debug_enter,
+	.fb_debug_leave	= drm_fb_helper_debug_leave,
+};
+
+static int armada_fb_create(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	struct drm_device *dev = fbh->dev;
+	struct drm_mode_fb_cmd2 mode;
+	struct armada_framebuffer *dfb;
+	struct armada_gem_object *obj;
+	struct fb_info *info;
+	int size, ret;
+
+	memset(&mode, 0, sizeof(mode));
+	mode.width = sizes->surface_width;
+	mode.height = sizes->surface_height;
+	mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp);
+	mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+					sizes->surface_depth);
+
+	size = ALIGN(mode.pitches[0] * mode.height, PAGE_SIZE);
+	obj = armada_gem_alloc_private_object(dev, size);
+	if (!obj) {
+		DRM_ERROR("failed to allocate fb memory\n");
+		return -ENOMEM;
+	}
+
+	ret = armada_gem_linear_back(dev, obj);
+	if (ret) {
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return ret;
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, &mode, obj);
+	if (ret)
+		goto err_fbcreate;
+
+	mutex_lock(&dev->struct_mutex);
+
+	info = framebuffer_alloc(0, dev->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto err_fballoc;
+	}
+
+	ret = fb_alloc_cmap(&info->cmap, 256, 0);
+	if (ret) {
+		ret = -ENOMEM;
+		goto err_fbcmap;
+	}
+
+	strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id));
+	info->par = fbh;
+	info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+	info->fbops = &armada_fb_ops;
+	info->fix.smem_start = obj->phys_addr;
+	info->fix.smem_len = size;
+	info->screen_size = size;
+	info->screen_base = ioremap_wc(info->fix.smem_start, size);
+	if (!info->screen_base) {
+		ret = -ENOMEM;
+		goto err_ioremap;
+	}
+
+	fbh->fb = &dfb->fb;
+	fbh->fbdev = info;
+	drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth);
+	drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height);
+
+	DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08x\n",
+		dfb->fb.width, dfb->fb.height,
+		dfb->fb.bits_per_pixel, obj->phys_addr);
+
+	drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+
+	return 0;
+
+ err_ioremap:
+	fb_dealloc_cmap(&info->cmap);
+ err_fbcmap:
+	framebuffer_release(info);
+ err_fballoc:
+	mutex_unlock(&dev->struct_mutex);
+	dfb->fb.funcs->destroy(&dfb->fb);
+ err_fbcreate:
+	drm_gem_object_unreference_unlocked(&obj->obj);
+	return ret;
+}
+
+static void armada_fb_destroy(struct drm_fb_helper *fbh)
+{
+	struct fb_info *info = fbh->fbdev;
+
+	if (info) {
+		unregister_framebuffer(info);
+		iounmap(info->screen_base);
+		if (info->cmap.len)
+			fb_dealloc_cmap(&info->cmap);
+		framebuffer_release(info);
+	}
+
+	if (fbh->fb)
+		fbh->fb->funcs->destroy(fbh->fb);
+
+	drm_fb_helper_fini(fbh);
+}
+
+static int armada_fb_probe(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	int ret = 0;
+
+	if (!fbh->fb) {
+		ret = armada_fb_create(fbh, sizes);
+		if (ret == 0)
+			ret = 1;
+	}
+	return ret;
+}
+
+static struct drm_fb_helper_funcs armada_fb_helper_funcs = {
+	.gamma_set	= armada_drm_crtc_gamma_set,
+	.gamma_get	= armada_drm_crtc_gamma_get,
+	.fb_probe	= armada_fb_probe,
+};
+
+int armada_fbdev_init(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh;
+	int ret;
+
+	fbh = kzalloc(sizeof(*fbh), GFP_KERNEL);
+	if (!fbh)
+		return -ENOMEM;
+
+	priv->fbdev = fbh;
+
+	fbh->funcs = &armada_fb_helper_funcs;
+
+	ret = drm_fb_helper_init(dev, fbh, 1, 1);
+	if (ret) {
+		DRM_ERROR("failed to initialize drm fb helper\n");
+		goto err_fb_helper;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(fbh);
+	if (ret) {
+		DRM_ERROR("failed to add fb connectors\n");
+		goto err_fb_setup;
+	}
+
+	ret = drm_fb_helper_initial_config(fbh, 32);
+	if (ret) {
+		DRM_ERROR("failed to set initial config\n");
+		goto err_fb_setup;
+	}
+
+	return 0;
+ err_fb_setup:
+	drm_fb_helper_fini(fbh);
+ err_fb_helper:
+	kfree(fbh);
+	priv->fbdev = NULL;
+	return ret;
+}
+
+void armada_fbdev_fini(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh) {
+		armada_fb_destroy(fbh);
+		kfree(fbh);
+		priv->fbdev = NULL;
+	}
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
new file mode 100644
index 0000000..1d9c9cb
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.c
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/shmem_fs.h>
+#include <drm/drmP.h>
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+static int armada_gem_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct armada_gem_object *obj = drm_to_armada_gem(vma->vm_private_data);
+	unsigned long addr = (unsigned long)vmf->virtual_address;
+	unsigned long pfn = obj->phys_addr >> PAGE_SHIFT;
+	int ret;
+
+	pfn += (addr - vma->vm_start) >> PAGE_SHIFT;
+	ret = vm_insert_pfn(vma, addr, pfn);
+
+	switch (ret) {
+	case -EIO:
+	case -EAGAIN:
+		set_need_resched();
+	case 0:
+	case -ERESTARTSYS:
+	case -EINTR:
+		return VM_FAULT_NOPAGE;
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	default:
+		return VM_FAULT_SIGBUS;
+	}
+}
+
+const struct vm_operations_struct armada_gem_vm_ops = {
+	.fault	= armada_gem_vm_fault,
+	.open	= drm_gem_vm_open,
+	.close	= drm_gem_vm_close,
+};
+
+static size_t roundup_gem_size(size_t size)
+{
+	return roundup(size, PAGE_SIZE);
+}
+
+void armada_gem_free_object(struct drm_gem_object *obj)
+{
+	struct armada_gem_object *dobj = drm_to_armada_gem(obj);
+
+	DRM_DEBUG_DRIVER("release obj %p\n", dobj);
+
+	if (dobj->linear)
+		drm_mm_put_block(dobj->linear);
+
+	if (dobj->obj.map_list.map)
+		drm_gem_free_mmap_offset(&dobj->obj);
+
+	if (dobj->page) {
+		unsigned int order = get_order(dobj->obj.size);
+		__free_pages(dobj->page, order);
+	}
+
+	drm_gem_object_release(&dobj->obj);
+
+	kfree(dobj);
+}
+
+int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
+{
+	struct armada_private *priv = dev->dev_private;
+	size_t size = obj->obj.size;
+
+	if (obj->page || obj->linear)
+		return 0;
+
+	/* If it is a small allocation, try to get it from the system */
+	if (size < 1048576) {
+		unsigned int order = get_order(size);
+		struct page *p = alloc_pages(GFP_KERNEL, order);
+
+		if (p) {
+			unsigned sz = (size + 31) & ~31;
+			uintptr_t ptr;
+
+			obj->phys_addr = page_to_phys(p);
+			obj->dev_addr = obj->phys_addr;
+
+			/* FIXME: Hack around dirty cache */
+			ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
+			memset((void *)ptr, 0, PAGE_ALIGN(size));
+			while (sz) {
+				asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
+				ptr += 32;
+				sz -= 32;
+			}
+			dsb();
+
+			obj->page = p;
+		}
+	}
+
+	/* Otherwise, grab it from our linear allocation */
+	if (!obj->page) {
+		struct drm_mm_node *free;
+		unsigned align = min_t(unsigned, size, SZ_2M);
+		void __iomem *ptr;
+
+		free = drm_mm_search_free(&priv->linear, size, align, 0);
+		if (free)
+			obj->linear = drm_mm_get_block(free, size, align);
+		if (!obj->linear)
+			return -ENOSPC;
+
+		/* Ensure that the memory we're returning is cleared. */
+		ptr = ioremap_wc(obj->linear->start, size);
+		if (!ptr) {
+			drm_mm_put_block(obj->linear);
+			obj->linear = NULL;
+			return -ENOMEM;
+		}
+
+		memset_io(ptr, 0, size);
+		iounmap(ptr);
+
+		obj->phys_addr = obj->linear->start;
+		obj->dev_addr = obj->linear->start;
+	}
+
+	DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n",
+			 obj, obj->phys_addr, obj->dev_addr);
+
+	return 0;
+}
+
+struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *dev,
+	size_t size)
+{
+	struct armada_gem_object *obj;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_private_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	DRM_DEBUG_DRIVER("alloc private obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+struct armada_gem_object *armada_gem_alloc_object(struct drm_device *dev,
+	size_t size)
+{
+	struct armada_gem_object *obj;
+	struct address_space *mapping;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	mapping = obj->obj.filp->f_path.dentry->d_inode->i_mapping;
+	mapping_set_gfp_mask(mapping, GFP_HIGHUSER | __GFP_RECLAIMABLE);
+
+	DRM_DEBUG_DRIVER("alloc obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+/* Dumb alloc support */
+int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
+	struct drm_mode_create_dumb *args)
+{
+	struct armada_gem_object *dobj;
+	u32 handle;
+	size_t size;
+	int ret;
+
+	args->pitch = armada_pitch(args->width, args->bpp);
+	args->size = size = args->pitch * args->height;
+
+	dobj = armada_gem_alloc_private_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = armada_gem_linear_back(dev, dobj);
+	if (ret)
+		goto err;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle, uint64_t *offset)
+{
+	struct armada_gem_object *obj;
+	int ret = 0;
+
+	mutex_lock(&dev->struct_mutex);
+	obj = armada_gem_object_lookup(dev, file, handle);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		ret = -EINVAL;
+		goto err_unlock;
+	}
+
+	if (!obj->obj.map_list.map)
+		ret = drm_gem_create_mmap_offset(&obj->obj);
+
+	if (ret == 0) {
+		*offset = (u64)obj->obj.map_list.hash.key << PAGE_SHIFT;
+		DRM_DEBUG_DRIVER("handle %#x offset %llx\n", handle, *offset);
+	}
+
+	drm_gem_object_unreference(&obj->obj);
+ err_unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle)
+{
+	return drm_gem_handle_delete(file, handle);
+}
+
+/* Private driver gem ioctls */
+int armada_gem_create_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create *args = data;
+	struct armada_gem_object *dobj;
+	size_t size;
+	u32 handle;
+	int ret;
+
+	if (args->size == 0) {
+		args->pitch = armada_pitch(args->width, args->bpp);
+		args->size = size = args->pitch * args->height;
+	} else {
+		size = args->size;
+	}
+
+	dobj = armada_gem_alloc_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_create_phys_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create_phys *arg = data;
+	struct armada_gem_object *dobj;
+	u32 handle;
+	int ret;
+
+	dobj = armada_gem_alloc_private_object(dev, arg->size);
+	if (dobj) {
+		dobj->phys_addr = arg->phys;
+		dobj->dev_addr = arg->phys;
+	}
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret) {
+		drm_gem_object_release(&dobj->obj);
+		return ret;
+	}
+
+	/* drop reference from allocate - handle holds it now */
+	drm_gem_object_unreference(&dobj->obj);
+
+	arg->handle = handle;
+
+	return 0;
+}
+
+/* Map a shmem-backed object into process memory space */
+int armada_gem_mmap_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_mmap *args = data;
+	struct armada_gem_object *dobj;
+	unsigned long addr;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	if (!dobj->obj.filp) {
+		drm_gem_object_unreference(&dobj->obj);
+		return -EINVAL;
+	}
+
+	addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE,
+	               MAP_SHARED, args->offset);
+	drm_gem_object_unreference(&dobj->obj);
+	if (IS_ERR_VALUE(addr))
+		return addr;
+
+	args->addr = addr;
+
+	return 0;
+}
+
+int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_prop *arg = data;
+	struct armada_gem_object *dobj;
+	int ret;
+
+	ret = mutex_lock_interruptible(&dev->struct_mutex);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, arg->handle);
+	if (dobj == NULL) {
+		ret = -ENOENT;
+		goto unlock;
+	}
+
+	arg->phys = dobj->phys_addr;
+	ret = 0;
+	drm_gem_object_unreference(&dobj->obj);
+
+ unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_pwrite *args = data;
+	struct armada_gem_object *dobj;
+	char __user *ptr;
+	int ret;
+
+	DRM_DEBUG_DRIVER("handle %u off %u size %u ptr 0x%llx\n",
+		args->handle, args->offset, args->size, args->ptr);
+
+	if (args->size == 0)
+		return 0;
+
+	ptr = (char __user *)(uintptr_t)args->ptr;
+
+	if (!access_ok(VERIFY_READ, ptr, args->size))
+		return -EFAULT;
+
+	ret = fault_in_multipages_readable(ptr, args->size);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	/* Don't allow pwrite to drm linear backed objects */
+	if (dobj->linear)
+		return -EINVAL;
+
+	if (args->offset > dobj->obj.size ||
+	    args->size > dobj->obj.size - args->offset) {
+		DRM_ERROR("invalid size: object size %u\n", dobj->obj.size);
+		ret = -EINVAL;
+		goto unref;
+	}
+
+	{
+		void *data = phys_to_virt(dobj->phys_addr) + args->offset;
+		ret = copy_from_user(data, ptr, args->size) ? -EFAULT : 0;
+
+		if (ret == 0 && dobj->update)
+			dobj->update(dobj->update_data);
+	}
+
+  unref:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.h b/drivers/gpu/drm/armada/armada_gem.h
new file mode 100644
index 0000000..f500546
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_GEM_H
+#define ARMADA_GEM_H
+
+/* GEM */
+struct armada_gem_object {
+	struct drm_gem_object	obj;
+	struct drm_mm_node	*linear;
+	phys_addr_t		phys_addr;
+	resource_size_t		dev_addr;
+	struct page		*page;
+	void			(*update)(void *);
+	void			*update_data;
+};
+
+#define drm_to_armada_gem(o) container_of(o, struct armada_gem_object, obj)
+
+void armada_gem_free_object(struct drm_gem_object *);
+int armada_gem_linear_back(struct drm_device *, struct armada_gem_object *);
+struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *, size_t);
+int armada_gem_dumb_create(struct drm_file *, struct drm_device *,
+	struct drm_mode_create_dumb *);
+int armada_gem_dumb_map_offset(struct drm_file *, struct drm_device *,
+	uint32_t, uint64_t *);
+int armada_gem_dumb_destroy(struct drm_file *, struct drm_device *,
+	uint32_t);
+
+static inline struct armada_gem_object *armada_gem_object_lookup(
+	struct drm_device *dev, struct drm_file *dfile, unsigned handle)
+{
+	struct drm_gem_object *obj = drm_gem_object_lookup(dev, dfile, handle);
+
+	return obj ? drm_to_armada_gem(obj) : NULL;
+}
+#endif
diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h
new file mode 100644
index 0000000..f9e539b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_hw.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_HW_H
+#define ARMADA_HW_H
+
+/*
+ * Note: the following registers are written from IRQ context:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *  LCD_SPU_DMA_START_ADDR_[YUV][01], LCD_SPU_DMA_PITCH_YC,
+ *  LCD_SPU_DMA_PITCH_UV, LCD_SPU_DMA_OVSA_HPXL_VLN,
+ *  LCD_SPU_DMA_HPXL_VLN, LCD_SPU_DZM_HPXL_VLN, LCD_SPU_DMA_CTRL0
+ */
+enum {
+	LCD_SPU_ADV_REG			= 0x0084,
+	LCD_SPU_DMA_START_ADDR_Y0	= 0x00c0,
+	LCD_SPU_DMA_START_ADDR_U0	= 0x00c4,
+	LCD_SPU_DMA_START_ADDR_V0	= 0x00c8,
+	LCD_CFG_DMA_START_ADDR_0	= 0x00cc,
+	LCD_SPU_DMA_START_ADDR_Y1	= 0x00d0,
+	LCD_SPU_DMA_START_ADDR_U1	= 0x00d4,
+	LCD_SPU_DMA_START_ADDR_V1	= 0x00d8,
+	LCD_CFG_DMA_START_ADDR_1	= 0x00dc,
+	LCD_SPU_DMA_PITCH_YC		= 0x00e0,
+	LCD_SPU_DMA_PITCH_UV		= 0x00e4,
+	LCD_SPU_DMA_OVSA_HPXL_VLN	= 0x00e8,
+	LCD_SPU_DMA_HPXL_VLN		= 0x00ec,
+	LCD_SPU_DZM_HPXL_VLN		= 0x00f0,
+	LCD_CFG_GRA_START_ADDR0		= 0x00f4,
+	LCD_CFG_GRA_START_ADDR1		= 0x00f8,
+	LCD_CFG_GRA_PITCH		= 0x00fc,
+	LCD_SPU_GRA_OVSA_HPXL_VLN	= 0x0100,
+	LCD_SPU_GRA_HPXL_VLN		= 0x0104,
+	LCD_SPU_GZM_HPXL_VLN		= 0x0108,
+	LCD_SPU_HWC_OVSA_HPXL_VLN	= 0x010c,
+	LCD_SPU_HWC_HPXL_VLN		= 0x0110,
+	LCD_SPUT_V_H_TOTAL		= 0x0114,
+	LCD_SPU_V_H_ACTIVE		= 0x0118,
+	LCD_SPU_H_PORCH			= 0x011c,
+	LCD_SPU_V_PORCH			= 0x0120,
+	LCD_SPU_BLANKCOLOR		= 0x0124,
+	LCD_SPU_ALPHA_COLOR1		= 0x0128,
+	LCD_SPU_ALPHA_COLOR2		= 0x012c,
+	LCD_SPU_COLORKEY_Y		= 0x0130,
+	LCD_SPU_COLORKEY_U		= 0x0134,
+	LCD_SPU_COLORKEY_V		= 0x0138,
+	LCD_CFG_RDREG4F			= 0x013c,
+	LCD_SPU_SPI_RXDATA		= 0x0140,
+	LCD_SPU_ISA_RSDATA		= 0x0144,
+	LCD_SPU_HWC_RDDAT		= 0x0158,
+	LCD_SPU_GAMMA_RDDAT		= 0x015c,
+	LCD_SPU_PALETTE_RDDAT		= 0x0160,
+	LCD_SPU_IOPAD_IN		= 0x0178,
+	LCD_CFG_RDREG5F			= 0x017c,
+	LCD_SPU_SPI_CTRL		= 0x0180,
+	LCD_SPU_SPI_TXDATA		= 0x0184,
+	LCD_SPU_SMPN_CTRL		= 0x0188,
+	LCD_SPU_DMA_CTRL0		= 0x0190,
+	LCD_SPU_DMA_CTRL1		= 0x0194,
+	LCD_SPU_SRAM_CTRL		= 0x0198,
+	LCD_SPU_SRAM_WRDAT		= 0x019c,
+	LCD_SPU_SRAM_PARA0		= 0x01a0,
+	LCD_SPU_SRAM_PARA1		= 0x01a4,
+	LCD_CFG_SCLK_DIV		= 0x01a8,
+	LCD_SPU_CONTRAST		= 0x01ac,
+	LCD_SPU_SATURATION		= 0x01b0,
+	LCD_SPU_CBSH_HUE		= 0x01b4,
+	LCD_SPU_DUMB_CTRL		= 0x01b8,
+	LCD_SPU_IOPAD_CONTROL		= 0x01bc,
+	LCD_SPU_IRQ_ENA			= 0x01c0,
+	LCD_SPU_IRQ_ISR			= 0x01c4,
+};
+
+/* For LCD_SPU_ADV_REG */
+enum {
+	ADV_VSYNC_L_OFF	= 0xfff << 20,
+	ADV_GRACOLORKEY	= 1 << 19,
+	ADV_VIDCOLORKEY	= 1 << 18,
+	ADV_HWC32BLEND	= 1 << 15,
+	ADV_HWC32ARGB	= 1 << 14,
+	ADV_HWC32ENABLE	= 1 << 13,
+	ADV_VSYNCOFFEN	= 1 << 12,
+	ADV_VSYNC_H_OFF	= 0xfff << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL0 */
+enum {
+	CFG_NOBLENDING	= 1 << 31,
+	CFG_GAMMA_ENA	= 1 << 30,
+	CFG_CBSH_ENA	= 1 << 29,
+	CFG_PALETTE_ENA	= 1 << 28,
+	CFG_ARBFAST_ENA	= 1 << 27,
+	CFG_HWC_1BITMOD	= 1 << 26,
+	CFG_HWC_1BITENA	= 1 << 25,
+	CFG_HWC_ENA	= 1 << 24,
+	CFG_DMAFORMAT	= 0xf << 20,
+	CFG_DMA_565	= 0x0 << 20,
+	CFG_DMA_1555	= 0x1 << 20,
+	CFG_DMA_888PACK	= 0x2 << 20,
+	CFG_DMA_X888	= 0x3 << 20,
+	CFG_DMA_8888	= 0x4 << 20,
+	CFG_DMA_422PACK	= 0x5 << 20,
+	CFG_DMA_422	= 0x6 << 20,
+	CFG_DMA_420	= 0x7 << 20,
+	CFG_DMA_PSEUDO4	= 0x9 << 20,
+	CFG_DMA_PSEUDO8	= 0xa << 20,
+	CFG_GRAFORMAT	= 0xf << 16,
+	CFG_GRA_565	= 0x0 << 16,
+	CFG_GRA_1555	= 0x1 << 16,
+	CFG_GRA_888PACK	= 0x2 << 16,
+	CFG_GRA_X888	= 0x3 << 16,
+	CFG_GRA_8888	= 0x4 << 16,
+	CFG_GRA_422PACK	= 0x5 << 16,
+	CFG_GRA_422	= 0x6 << 16,
+	CFG_GRA_420	= 0x7 << 16,
+	CFG_GRA_PSEUDO4	= 0x9 << 16,
+	CFG_GRA_PSEUDO8	= 0xa << 16,
+	CFG_GRA_FTOGGLE	= 1 << 15,
+	CFG_GRA_HSMOOTH	= 1 << 14,
+	CFG_GRA_TSTMODE	= 1 << 13,
+	CFG_GRA_SWAPRB	= 1 << 12,
+	CFG_GRA_SWAPUV	= 1 << 11,
+	CFG_GRA_SWAPYU	= 1 << 10,
+	CFG_YUV2RGB_GRA	= 1 << 9,
+	CFG_GRA_ENA	= 1 << 8,
+	CFG_DMA_FTOGGLE	= 1 << 7,
+	CFG_DMA_HSMOOTH	= 1 << 6,
+	CFG_DMA_TSTMODE	= 1 << 5,
+	CFG_DMA_SWAPRB	= 1 << 4,
+	CFG_DMA_SWAPUV	= 1 << 3,
+	CFG_DMA_SWAPYU	= 1 << 2,
+	CFG_YUV2RGB_DMA	= 1 << 1,
+	CFG_DMA_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL1 */
+enum {
+	CFG_FRAME_TRIG	= 1 << 31,
+	CFG_VSYNC_INV	= 1 << 27,
+	CFG_CKMODE_MASK	= 0x7 << 24,
+	CFG_CKMODE_DIS	= 0x0 << 24,
+	CFG_CKMODE_Y	= 0x1 << 24,
+	CFG_CKMODE_U	= 0x2 << 24,
+	CFG_CKMODE_RGB	= 0x3 << 24,
+	CFG_CKMODE_V	= 0x4 << 24,
+	CFG_CKMODE_R	= 0x5 << 24,
+	CFG_CKMODE_G	= 0x6 << 24,
+	CFG_CKMODE_B	= 0x7 << 24,
+	CFG_CARRY	= 1 << 23,
+	CFG_GATED_CLK	= 1 << 21,
+	CFG_PWRDN_ENA	= 1 << 20,
+	CFG_DSCALE_MASK	= 0x3 << 18,
+	CFG_DSCALE_NONE	= 0x0 << 18,
+	CFG_DSCALE_HALF	= 0x1 << 18,
+	CFG_DSCALE_QUAR = 0x2 << 18,
+	CFG_ALPHAM_MASK	= 0x3 << 16,
+	CFG_ALPHAM_VIDEO= 0x0 << 16,
+	CFG_ALPHAM_GRA	= 0x1 << 16,
+	CFG_ALPHAM_CFG	= 0x2 << 16,
+	CFG_ALPHA_MASK	= 0xff << 8,
+	CFG_PIXCMD_MASK	= 0xff,
+};
+
+/* For LCD_SPU_SRAM_CTRL */
+enum {
+	SRAM_READ	= 0 << 14,
+	SRAM_WRITE	= 2 << 14,
+	SRAM_INIT	= 3 << 14,
+	SRAM_HWC32_RAMR	= 0xc << 8,
+	SRAM_HWC32_RAMG	= 0xd << 8,
+	SRAM_HWC32_RAMB	= 0xe << 8,
+	SRAM_HWC32_TRAN	= 0xf << 8,
+	SRAM_HWC	= 0xf << 8,
+};
+
+/* For LCD_SPU_SRAM_PARA1 */
+enum {
+	CFG_CSB_256x32	= 1 << 15,
+	CFG_CSB_256x24	= 1 << 14,
+	CFG_CSB_256x8	= 1 << 13,
+	CFG_PDWN256x32	= 1 << 7,
+	CFG_PDWN256x24	= 1 << 6,
+	CFG_PDWN256x8	= 1 << 5,
+	CFG_PDWN32x32	= 1 << 3,
+	CFG_PDWN16x66	= 1 << 2,
+	CFG_PDWN32x66	= 1 << 1,
+	CFG_PDWN64x66	= 1 << 0,
+};
+
+/* For LCD_SPU_DUMB_CTRL */
+enum {
+	DUMB16_RGB565_0	= 0x0 << 28,
+	DUMB16_RGB565_1	= 0x1 << 28,
+	DUMB18_RGB666_0	= 0x2 << 28,
+	DUMB18_RGB666_1	= 0x3 << 28,
+	DUMB12_RGB444_0	= 0x4 << 28,
+	DUMB12_RGB444_1	= 0x5 << 28,
+	DUMB24_RGB888_0	= 0x6 << 28,
+	DUMB_BLANK	= 0x7 << 28,
+	DUMB_MASK	= 0xf << 28,
+	CFG_BIAS_OUT	= 1 << 8,
+	CFG_REV_RGB	= 1 << 7,
+	CFG_INV_CBLANK	= 1 << 6,
+	CFG_INV_CSYNC	= 1 << 5,
+	CFG_INV_HENA	= 1 << 4,	/* Normally active high */
+	CFG_INV_VSYNC	= 1 << 3,	/* Normally active high */
+	CFG_INV_HSYNC	= 1 << 2,	/* Normally active high */
+	CFG_INV_PCLK	= 1 << 1,
+	CFG_DUMB_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_IOPAD_CONTROL */
+enum {
+	CFG_VSCALE_LN_EN	= 3 << 18,
+	CFG_GRA_VM_ENA		= 1 << 15,
+	CFG_DMA_VM_ENA		= 1 << 13,
+	CFG_CMD_VM_ENA		= 1 << 11,
+	CFG_CSC_MASK		= 3 << 8,
+	CFG_CSC_CCIR709		= 1 << 9,
+	CFG_CSC_PROF		= 1 << 8,
+	CFG_IOPAD_MASK		= 0xf << 0,
+	CFG_IOPAD_DUMB24	= 0x0 << 0,
+	CFG_IOPAD_DUMB18SPI	= 0x1 << 0,
+	CFG_IOPAD_DUMB18GPIO	= 0x2 << 0,
+	CFG_IOPAD_DUMB16SPI	= 0x3 << 0,
+	CFG_IOPAD_DUMB16GPIO	= 0x4 << 0,
+	CFG_IOPAD_DUMB12GPIO	= 0x5 << 0,
+	CFG_IOPAD_SMART18	= 0x6 << 0,
+	CFG_IOPAD_SMART16	= 0x7 << 0,
+	CFG_IOPAD_SMART8	= 0x8 << 0,
+};
+
+#define IOPAD_DUMB24                0x0
+
+/* For LCD_SPU_IRQ_ENA */
+enum {
+	DMA_FRAME_IRQ0_ENA	= 1 << 31,
+	DMA_FRAME_IRQ1_ENA	= 1 << 30,
+	DMA_FRAME_IRQ_ENA	= DMA_FRAME_IRQ0_ENA | DMA_FRAME_IRQ1_ENA,
+	DMA_FF_UNDERFLOW_ENA	= 1 << 29,
+	GRA_FRAME_IRQ0_ENA	= 1 << 27,
+	GRA_FRAME_IRQ1_ENA	= 1 << 26,
+	GRA_FRAME_IRQ_ENA	= GRA_FRAME_IRQ0_ENA | GRA_FRAME_IRQ1_ENA,
+	GRA_FF_UNDERFLOW_ENA	= 1 << 25,
+	VSYNC_IRQ_ENA		= 1 << 23,
+	DUMB_FRAMEDONE_ENA	= 1 << 22,
+	TWC_FRAMEDONE_ENA	= 1 << 21,
+	HWC_FRAMEDONE_ENA	= 1 << 20,
+	SLV_IRQ_ENA		= 1 << 19,
+	SPI_IRQ_ENA		= 1 << 18,
+	PWRDN_IRQ_ENA		= 1 << 17,
+	ERR_IRQ_ENA		= 1 << 16,
+	CLEAN_SPU_IRQ_ISR	= 0xffff,
+};
+
+/* For LCD_SPU_IRQ_ISR */
+enum {
+	DMA_FRAME_IRQ0		= 1 << 31,
+	DMA_FRAME_IRQ1		= 1 << 30,
+	DMA_FRAME_IRQ		= DMA_FRAME_IRQ0 | DMA_FRAME_IRQ1,
+	DMA_FF_UNDERFLOW	= 1 << 29,
+	GRA_FRAME_IRQ0		= 1 << 27,
+	GRA_FRAME_IRQ1		= 1 << 26,
+	GRA_FRAME_IRQ		= GRA_FRAME_IRQ0 | GRA_FRAME_IRQ1,
+	GRA_FF_UNDERFLOW	= 1 << 25,
+	VSYNC_IRQ		= 1 << 23,
+	DUMB_FRAMEDONE		= 1 << 22,
+	TWC_FRAMEDONE		= 1 << 21,
+	HWC_FRAMEDONE		= 1 << 20,
+	SLV_IRQ			= 1 << 19,
+	SPI_IRQ			= 1 << 18,
+	PWRDN_IRQ		= 1 << 17,
+	ERR_IRQ			= 1 << 16,
+	DMA_FRAME_IRQ0_LEVEL	= 1 << 15,
+	DMA_FRAME_IRQ1_LEVEL	= 1 << 14,
+	DMA_FRAME_CNT_ISR	= 3 << 12,
+	GRA_FRAME_IRQ0_LEVEL	= 1 << 11,
+	GRA_FRAME_IRQ1_LEVEL	= 1 << 10,
+	GRA_FRAME_CNT_ISR	= 3 << 8,
+	VSYNC_IRQ_LEVEL		= 1 << 7,
+	DUMB_FRAMEDONE_LEVEL	= 1 << 6,
+	TWC_FRAMEDONE_LEVEL	= 1 << 5,
+	HWC_FRAMEDONE_LEVEL	= 1 << 4,
+	SLV_FF_EMPTY		= 1 << 3,
+	DMA_FF_ALLEMPTY		= 1 << 2,
+	GRA_FF_ALLEMPTY		= 1 << 1,
+	PWRDN_IRQ_LEVEL		= 1 << 0,
+};
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctl.h b/drivers/gpu/drm/armada/armada_ioctl.h
new file mode 100644
index 0000000..619ec2c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctl.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  With inspiration from the i915 driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_ARMADA_IOCTL_H
+#define DRM_ARMADA_IOCTL_H
+
+#define DRM_ARMADA_GEM_CREATE		0x00
+#define DRM_ARMADA_GEM_CREATE_PHYS	0x01
+#define DRM_ARMADA_GEM_MMAP		0x02
+#define DRM_ARMADA_GEM_PWRITE		0x03
+#define DRM_ARMADA_GEM_PROP		0x04
+#define DRM_ARMADA_OVERLAY_PUT_IMAGE	0x06
+#define DRM_ARMADA_OVERLAY_ATTRS		0x07
+
+#define ARMADA_IOCTL(dir,name,str) \
+	DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
+
+struct drm_armada_gem_create {
+	uint32_t height;
+	uint32_t width;
+	uint32_t bpp;
+	uint32_t handle;
+	uint32_t pitch;
+	uint32_t size;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE \
+	ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
+
+struct drm_armada_gem_create_phys {
+	uint32_t size;
+	uint32_t handle;
+	uint64_t phys;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
+	ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
+
+struct drm_armada_gem_mmap {
+	uint32_t handle;
+	uint32_t pad;
+	uint64_t offset;
+	uint64_t size;
+	uint64_t addr;
+};
+#define DRM_IOCTL_ARMADA_GEM_MMAP \
+	ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
+
+struct drm_armada_gem_pwrite {
+	uint32_t handle;
+	uint32_t offset;
+	uint32_t size;
+	uint64_t ptr;
+};
+#define DRM_IOCTL_ARMADA_GEM_PWRITE \
+	ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
+
+struct drm_armada_gem_prop {
+	uint64_t phys;
+	uint32_t handle;
+};
+#define DRM_IOCTL_ARMADA_GEM_PROP \
+	ARMADA_IOCTL(IOWR, GEM_PROP, gem_prop)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_put_image {
+	uint32_t flags;
+#define ARMADA_OVERLAY_TYPE_MASK          0x000000ff
+#define ARMADA_OVERLAY_YUV_PLANAR         0x00000001
+#define ARMADA_OVERLAY_YUV_PACKED         0x00000002
+#define ARMADA_OVERLAY_RGB                0x00000003
+#define ARMADA_OVERLAY_DEPTH_MASK		0x0000ff00
+#define ARMADA_OVERLAY_RGB24		0x00001000
+#define ARMADA_OVERLAY_RGB16		0x00002000
+#define ARMADA_OVERLAY_RGB15		0x00003000
+#define ARMADA_OVERLAY_YUV422		0x00000100
+#define ARMADA_OVERLAY_YUV411		0x00000200
+#define ARMADA_OVERLAY_YUV420		0x00000300
+#define ARMADA_OVERLAY_YUV410		0x00000400
+#define ARMADA_OVERLAY_SWAP_MASK		0x00ff0000
+#define ARMADA_OVERLAY_NO_SWAP		0x00000000
+#define ARMADA_OVERLAY_UV_SWAP		0x00010000
+#define ARMADA_OVERLAY_Y_SWAP		0x00020000
+#define ARMADA_OVERLAY_Y_AND_UV_SWAP	0x00030000
+#define ARMADA_OVERLAY_FLAGS_MASK		0xff000000
+#define ARMADA_OVERLAY_ENABLE		0x01000000
+	uint32_t bo_handle;
+	uint16_t stride_Y;
+	uint16_t stride_UV;
+	uint32_t offset_Y;
+	uint32_t offset_U;
+	uint32_t offset_V;
+	uint16_t src_width;
+	uint16_t src_height;
+	uint16_t src_scan_width;
+	uint16_t src_scan_height;
+	uint32_t crtc_id;
+	uint16_t dst_x;
+	uint16_t dst_y;
+	uint16_t dst_width;
+	uint16_t dst_height;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_PUT_IMAGE \
+	ARMADA_IOCTL(IOW, OVERLAY_PUT_IMAGE, overlay_put_image)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_attrs {
+	uint32_t flags;
+#define ARMADA_OVERLAY_UPDATE_ATTRS	(1<<0)
+#define ARMADA_OVERLAY_UPDATE_GAMMA	(1<<1)
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+	uint32_t gamma0;
+	uint32_t gamma1;
+	uint32_t gamma2;
+	uint32_t gamma3;
+	uint32_t gamma4;
+	uint32_t gamma5;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_ATTRS \
+	ARMADA_IOCTL(IOWR, OVERLAY_ATTRS, overlay_attrs)
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctlP.h b/drivers/gpu/drm/armada/armada_ioctlP.h
new file mode 100644
index 0000000..8e6089e
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctlP.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_IOCTLP_H
+#define ARMADA_IOCTLP_H
+
+#define ARMADA_IOCTL_PROTO(name)\
+extern int armada_##name##_ioctl(struct drm_device *, void *, struct drm_file *)
+
+ARMADA_IOCTL_PROTO(gem_create);
+ARMADA_IOCTL_PROTO(gem_create_phys);
+ARMADA_IOCTL_PROTO(gem_mmap);
+ARMADA_IOCTL_PROTO(gem_prop);
+ARMADA_IOCTL_PROTO(gem_pwrite);
+ARMADA_IOCTL_PROTO(overlay_put_image);
+ARMADA_IOCTL_PROTO(overlay_attrs);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_output.c b/drivers/gpu/drm/armada/armada_output.c
new file mode 100644
index 0000000..3b95f0c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "drm_helper.h"
+#include "armada_output.h"
+#include "armada_drm.h"
+
+struct armada_connector {
+	struct drm_connector conn;
+	const struct armada_output_type *type;
+};
+
+#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = conn->encoder;
+
+	return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
+}
+
+static enum drm_connector_status armada_drm_connector_detect(
+	struct drm_connector *conn, bool force)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+	enum drm_connector_status status = connector_status_disconnected;
+
+	if (dconn->type->detect) {
+		status = dconn->type->detect(conn, force);
+	} else {
+		struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+
+		if (enc)
+			status = encoder_helper_funcs(enc)->detect(enc, conn);
+	}
+
+	return status;
+}
+
+static void armada_drm_connector_destroy(struct drm_connector *conn)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	drm_sysfs_connector_remove(conn);
+	drm_connector_cleanup(conn);
+	kfree(dconn);
+}
+
+static int armada_drm_connector_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	if (!dconn->type->set_property)
+		return -EINVAL;
+
+	return dconn->type->set_property(conn, property, value);
+}
+
+static const struct drm_connector_funcs armada_drm_conn_funcs = {
+	.dpms		= drm_helper_connector_dpms,
+	.fill_modes	= drm_helper_probe_single_connector_modes,
+	.detect		= armada_drm_connector_detect,
+	.destroy	= armada_drm_connector_destroy,
+	.set_property	= armada_drm_connector_set_property,
+};
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+void armada_drm_encoder_commit(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
+{
+	return true;
+}
+
+/* Shouldn't this be a generic helper function? */
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int valid = MODE_BAD;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		valid = slave->slave_funcs->mode_valid(encoder, mode);
+	}
+	return valid;
+}
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int rc = -EINVAL;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		rc = slave->slave_funcs->set_property(encoder, conn, property,
+						      value);
+	}
+	return rc;
+}
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data)
+{
+	struct armada_connector *dconn;
+	int ret;
+
+	dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
+	if (!dconn)
+		return -ENOMEM;
+
+	dconn->type = type;
+
+	ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
+				 type->connector_type);
+	if (ret) {
+		DRM_ERROR("unable to init connector\n");
+		goto err_destroy_dconn;
+	}
+
+	ret = type->create(&dconn->conn, data);
+	if (ret)
+		goto err_conn;
+
+	ret = drm_sysfs_connector_add(&dconn->conn);
+	if (ret)
+		goto err_sysfs;
+
+	return 0;
+
+ err_sysfs:
+	if (dconn->conn.encoder)
+		dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
+ err_conn:
+	drm_connector_cleanup(&dconn->conn);
+ err_destroy_dconn:
+	kfree(dconn);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_output.h b/drivers/gpu/drm/armada/armada_output.h
new file mode 100644
index 0000000..d655655
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CONNETOR_H
+#define ARMADA_CONNETOR_H
+
+#define encoder_helper_funcs(encoder) \
+	((struct drm_encoder_helper_funcs *)encoder->helper_private)
+
+struct armada_output_type {
+	int connector_type;
+	enum drm_connector_status (*detect)(struct drm_connector *, bool);
+	int (*create)(struct drm_connector *, const void *);
+	int (*set_property)(struct drm_connector *, struct drm_property *, uint64_t);
+};
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn);
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder);
+void armada_drm_encoder_commit(struct drm_encoder *encoder);
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj);
+
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode);
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value);
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c
new file mode 100644
index 0000000..68c4efd
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_overlay.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include "drm_helper.h"
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+struct armada_overlay {
+	struct armada_gem_object *obj;
+	struct armada_gem_object *old_obj;
+	struct armada_crtc *dcrtc;
+	uint32_t crtc_id;
+	uint32_t cached_flags;
+	uint32_t cached_cfg;
+
+	uint32_t stride_yc;
+	uint32_t stride_uv;
+	uint32_t dst_yx;
+	uint32_t src_hw;
+	uint32_t dst_hw;
+	uint32_t cfg;
+
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+
+	struct armada_vbl_event vbl_update;
+
+	struct armada_regs update[13];
+
+	wait_queue_head_t vbl_wait;
+};
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+static void armada_ovl_update(struct armada_crtc *dcrtc, void *data)
+{
+	struct armada_overlay *ovl = container_of(data, struct armada_overlay, update);
+	armada_drm_crtc_update_regs(dcrtc, data);
+	wake_up(&ovl->vbl_wait);
+}
+
+static void armada_ovl_update_attr(struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+	uint32_t key = ovl->color_key;
+        uint32_t r, g, b;
+
+        r = (key & 0x0000ff);
+        g = (key & 0x00ff00) >> 8;
+        b = (key & 0xff0000) >> 16;
+
+	writel_relaxed(r << 8 | r << 16 | r << 24, dcrtc->base + LCD_SPU_COLORKEY_Y);
+	writel_relaxed(g << 8 | g << 16 | g << 24, dcrtc->base + LCD_SPU_COLORKEY_U);
+	writel_relaxed(b << 8 | b << 16 | b << 24, dcrtc->base + LCD_SPU_COLORKEY_V);
+
+	writel_relaxed(0x00004000, dcrtc->base + LCD_SPU_CONTRAST);
+	writel_relaxed(0x40000000, dcrtc->base + LCD_SPU_SATURATION);
+	writel_relaxed(0x00002000, dcrtc->base + LCD_SPU_CBSH_HUE);
+
+	spin_lock_irq(&dcrtc->irq_lock);
+	armada_updatel(CFG_CKMODE_RGB | CFG_ALPHAM_GRA,
+		     CFG_CKMODE_MASK | CFG_ALPHAM_MASK | CFG_ALPHA_MASK,
+		     dcrtc->base + LCD_SPU_DMA_CTRL1);
+
+	armada_updatel(ADV_GRACOLORKEY, 0, dcrtc->base + LCD_SPU_ADV_REG);
+	spin_unlock_irq(&dcrtc->irq_lock);
+}
+
+static int armada_check_planar(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp;
+
+	DRM_DEBUG_DRIVER("stride Y%#x UV%#x offset Y%#x U%#x V%#x obj %#x\n",
+		args->stride_Y, args->stride_UV,
+		args->offset_Y, args->offset_U, args->offset_V,
+		obj->obj.size);
+
+	if (args->src_scan_width     > args->stride_Y ||
+	    args->src_scan_width / 2 > args->stride_UV)
+		return -EINVAL;
+
+	tmp = args->stride_Y * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	tmp = args->stride_UV * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_U > obj->obj.size - tmp ||
+	    args->offset_V > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int armada_check_packed(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp = args->stride_Y * args->src_height;
+
+	if (args->src_scan_width * 2 > args->stride_Y ||
+	    tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct armada_crtc *armada_crtc_lookup(struct drm_device *dev, uint32_t id)
+{
+	struct drm_crtc *crtc = drm_crtc_find(dev, id);
+	return crtc ? drm_to_armada_crtc(crtc) : NULL;
+}
+
+static void armada_ovl_release_old(struct armada_overlay *ovl)
+{
+	if (ovl->old_obj) {
+		drm_gem_object_unreference(&ovl->old_obj->obj);
+		ovl->old_obj = NULL;
+	}
+}
+
+/*
+ * This should be called with both dev->struct_mutex and
+ * the mode_config mutexes held.
+ */
+void armada_drm_overlay_off(struct drm_device *dev, struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+
+	ovl->cfg = 0;
+
+	if (dcrtc) {
+		/* Disable overlay */
+		armada_drm_vbl_event_remove_unlocked(dcrtc, &ovl->vbl_update);
+
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_updatel(0, CFG_DMA_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
+
+		ovl->dcrtc->overlay = NULL;
+		ovl->dcrtc = NULL;
+		ovl->crtc_id = ~0;
+	}
+
+	armada_ovl_release_old(ovl);
+
+	if (ovl->obj) {
+		drm_gem_object_unreference(&ovl->obj->obj);
+		ovl->obj = NULL;
+	}
+}
+
+static int armada_ovl_check_dst(const struct drm_armada_overlay_put_image *args,
+	const struct drm_display_mode *mode)
+{
+	if (args->dst_x < mode->hdisplay &&
+	    args->dst_width <= mode->hdisplay - args->dst_x &&
+	    args->dst_y < mode->vdisplay &&
+	    args->dst_height <= mode->vdisplay - args->dst_y)
+		return 0;
+	return -EINVAL;
+}
+
+static int armada_ovl_compute_cfg(struct armada_overlay *ovl, uint32_t flags)
+{
+	uint32_t cfg = CFG_DMA_HSMOOTH | CFG_CBSH_ENA;
+
+	switch (flags & ARMADA_OVERLAY_TYPE_MASK) {
+	case ARMADA_OVERLAY_YUV_PLANAR:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:	/* Planar YUV422 */
+			cfg |= CFG_DMA_422;
+			break;
+		case ARMADA_OVERLAY_YUV420:	/* Planar YUV420 */
+			cfg |= CFG_DMA_420;
+			break;
+		default:
+			DRM_ERROR("bad planar depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("planar and requested swap\n");
+			return -EINVAL;
+		}
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_YUV_PACKED:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:
+			cfg |= CFG_DMA_422PACK;
+			break;
+		default:
+			DRM_ERROR("bad packed depth\n");
+			return -EINVAL;
+		}
+		if (flags & (ARMADA_OVERLAY_SWAP_MASK & ~ARMADA_OVERLAY_Y_AND_UV_SWAP)) {
+			DRM_ERROR("bad overlay swap\n");
+			return -EINVAL;
+		}
+		/*
+		 *                            [0:7] [8:15] [16:23] [24:31]
+		 * 0:			YUY2:   Y     U       Y       V
+		 * UV_SWAP:		YVYU:   Y     V       Y       U
+		 * Y_SWAP:		UYVY:   U     Y       V       Y
+		 * Y_SWAP | UV_SWAP:	VYUY:   V     Y       U       Y
+		 *
+		 * Default ordering in memory:  U     Y0      V       Y1
+		 *
+		 * Image fourcc 59565955 UYVY flags 01020102 -> correct
+		 * Image fourcc 32595559 YUY2 flags 01000102 -> wrong U/V swapped
+		 */
+		if (flags & ARMADA_OVERLAY_UV_SWAP)
+			cfg |= CFG_DMA_SWAPUV;
+		if (!(flags & ARMADA_OVERLAY_Y_SWAP))
+			cfg ^= CFG_DMA_SWAPYU | CFG_DMA_SWAPUV;
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_RGB:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_RGB24:	/* 3 byte RGB */
+			cfg |= CFG_DMA_888PACK;
+			break;
+		case ARMADA_OVERLAY_RGB16:        /* 2 byte RGB */
+			cfg |= CFG_DMA_565;
+			break;
+		case ARMADA_OVERLAY_RGB15:        /* 2 byte RGB */
+			cfg |= CFG_DMA_1555;
+			break;
+		default:
+			DRM_ERROR("bad RGB depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("RGB and requested swap\n");
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		DRM_ERROR("bad overlay type\n");
+		return -EINVAL;
+	}
+
+	cfg |= CFG_DMA_ENA;
+
+	ovl->cached_flags = flags;
+	ovl->cached_cfg = cfg;
+	return 0;
+}
+
+int armada_overlay_put_image_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_put_image *args = data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+	struct armada_gem_object *obj;
+	struct armada_crtc *dcrtc;
+	uint32_t stride_uv, stride_yc, src_hw, dst_hw, dst_yx;
+	bool planar = false;
+	int ret, idx;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (!(args->flags & ARMADA_OVERLAY_ENABLE)) {
+		mutex_lock(&dev->mode_config.mutex);
+		mutex_lock(&dev->struct_mutex);
+
+		armada_drm_overlay_off(dev, ovl);
+
+		mutex_unlock(&dev->struct_mutex);
+		mutex_unlock(&dev->mode_config.mutex);
+
+		return 0;
+	}
+
+//	DRM_DEBUG_DRIVER("flags %x handle %x src %dx%d dst %dx%d+%d+%d\n",
+//		args->flags, args->bo_handle, args->src_scan_width, args->src_scan_height,
+//		args->dst_width, args->dst_height, args->dst_x, args->dst_y);
+
+	if (!ovl->dcrtc || ovl->crtc_id != args->crtc_id) {
+		dcrtc = armada_crtc_lookup(dev, args->crtc_id);
+		if (!dcrtc)
+			return -ENOENT;
+	} else {
+		dcrtc = ovl->dcrtc;
+	}
+
+	if (args->flags != ovl->cached_flags) {
+		ret = armada_ovl_compute_cfg(ovl, args->flags);
+		if (ret)
+			return ret;
+	}
+
+	obj = armada_gem_object_lookup(dev, file, args->bo_handle);
+	if (!obj)
+		return -ENOENT;
+
+	ret = wait_event_timeout(ovl->vbl_wait, list_empty(&ovl->vbl_update.node), HZ/25);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&dev->mode_config.mutex);
+	mutex_lock(&dev->struct_mutex);
+
+	/* Prevent updates */
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	if (ovl->dcrtc != dcrtc) {
+		armada_drm_overlay_off(dev, ovl);
+
+		ovl->crtc_id = args->crtc_id;
+		ovl->dcrtc = dcrtc;
+		dcrtc->overlay = ovl;
+
+		armada_ovl_update_attr(ovl);
+	}
+
+	ret = armada_ovl_check_dst(args, &ovl->dcrtc->crtc.mode);
+	if (ret)
+		goto err_unref;
+
+	planar = (args->flags & ARMADA_OVERLAY_TYPE_MASK) == ARMADA_OVERLAY_YUV_PLANAR;
+	if (planar)
+		ret = armada_check_planar(args, obj);
+	else
+		ret = armada_check_packed(args, obj);
+	if (ret)
+		goto err_unref;
+
+	if (ovl->dcrtc->interlaced) {
+		args->dst_y /= 2;
+		args->dst_height /= 2;
+	}
+
+	idx = 0;
+	if (ovl->obj != obj) {
+		uint32_t start_y, start_u, start_v;
+
+		/* switch to new object */
+		armada_ovl_release_old(ovl);
+		ovl->old_obj = ovl->obj;
+		ovl->obj = obj;
+
+		start_y = obj->dev_addr + args->offset_Y;
+		if (planar) {
+			start_u = obj->dev_addr + args->offset_U;
+			start_v = obj->dev_addr + args->offset_V;
+		} else {
+			start_u = start_y;
+			start_v = start_y;
+		}
+
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y0);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U0);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V0);
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y1);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U1);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V1);
+	} else {
+		drm_gem_object_unreference(&obj->obj);
+	}
+
+	stride_yc = args->stride_Y << 16 | args->stride_Y;
+	stride_uv = args->stride_UV << 16 | args->stride_UV;
+
+	if (ovl->stride_yc != stride_yc || ovl->stride_uv != stride_uv) {
+		ovl->stride_yc = stride_yc;
+		ovl->stride_uv = stride_uv;
+		armada_reg_queue_set(ovl->update, idx, stride_yc, LCD_SPU_DMA_PITCH_YC);
+		armada_reg_queue_set(ovl->update, idx, stride_uv, LCD_SPU_DMA_PITCH_UV);
+	}
+
+	src_hw = args->src_scan_height << 16 | args->src_scan_width;
+	dst_hw = args->dst_height << 16 | args->dst_width;
+	if (ovl->src_hw != src_hw || ovl->dst_hw != dst_hw) {
+		ovl->src_hw = src_hw;
+		ovl->dst_hw = dst_hw;
+
+		armada_reg_queue_set(ovl->update, idx, dst_hw, LCD_SPU_DZM_HPXL_VLN);
+		armada_reg_queue_set(ovl->update, idx, src_hw, LCD_SPU_DMA_HPXL_VLN);
+	}
+
+	dst_yx = args->dst_y << 16 | args->dst_x;
+	if (ovl->dst_yx != dst_yx) {
+		ovl->dst_yx = dst_yx;
+		armada_reg_queue_set(ovl->update, idx, dst_yx, LCD_SPU_DMA_OVSA_HPXL_VLN);
+	}
+
+	/* Update overlay DMA settings */
+	if (ovl->cfg != ovl->cached_cfg) {
+		ovl->cfg = ovl->cached_cfg;
+		armada_reg_queue_mod(ovl->update, idx, ovl->cached_cfg,
+			CFG_DMAFORMAT | CFG_DMA_FTOGGLE | CFG_DMA_HSMOOTH |
+			CFG_DMA_TSTMODE | CFG_DMA_SWAPRB | CFG_DMA_SWAPUV |
+			CFG_DMA_SWAPYU | CFG_YUV2RGB_DMA | CFG_DMA_ENA,
+			LCD_SPU_DMA_CTRL0);
+	}
+	if (idx) {
+		armada_reg_queue_end(ovl->update, idx);
+		armada_drm_vbl_event_add(dcrtc, &ovl->vbl_update);
+	}
+
+ err_unref:
+	if (ret)
+		drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+	mutex_unlock(&dev->mode_config.mutex);
+
+	return ret;
+}
+
+int armada_overlay_attrs_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_attrs *args = data;
+        struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_ATTRS) {
+		if (args->brightness < -128 || args->brightness > 127 ||
+		    args->contrast > 255 || args->saturation > 1023)
+			return -EINVAL;
+
+		ovl->color_key = args->color_key;
+		ovl->brightness = args->brightness;
+		ovl->contrast = args->contrast;
+		ovl->saturation = args->saturation;
+
+		mutex_lock(&dev->mode_config.mutex);
+		if (ovl->dcrtc)
+			armada_ovl_update_attr(ovl);
+		mutex_unlock(&dev->mode_config.mutex);
+	} else {
+		args->color_key = ovl->color_key;
+		args->brightness = ovl->brightness;
+		args->contrast = ovl->contrast;
+		args->saturation = ovl->saturation;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_GAMMA) {
+		/* args->gamma0..5 */
+	}
+
+	return 0;
+}
+
+int armada_overlay_create(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl;
+
+	ovl = kzalloc(sizeof(*ovl), GFP_KERNEL);
+	if (!ovl)
+		return -ENOMEM;
+
+	priv->overlay = ovl;
+
+	init_waitqueue_head(&ovl->vbl_wait);
+	armada_drm_vbl_event_init(&ovl->vbl_update, armada_ovl_update, ovl->update);
+
+	ovl->color_key = 0x0101fe;
+	ovl->brightness = -19;
+	ovl->contrast = 75;
+	ovl->saturation = 146;
+
+	return 0;
+}
+
+void armada_overlay_destroy(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	kfree(ovl);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.c b/drivers/gpu/drm/armada/armada_slave.c
new file mode 100644
index 0000000..422a345
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "armada_drm.h"
+#include "armada_output.h"
+#include "armada_slave.h"
+
+static int armada_drm_slave_get_modes(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+	int count = 0;
+
+	if (enc) {
+		struct drm_encoder_slave *slave = to_encoder_slave(enc);
+
+		count = slave->slave_funcs->get_modes(enc, conn);
+	}
+
+	return count;
+}
+
+static void armada_drm_slave_destroy(struct drm_encoder *enc)
+{
+	struct drm_encoder_slave *slave = to_encoder_slave(enc);
+	struct i2c_client *client = drm_i2c_encoder_get_client(enc);
+
+	if (slave->slave_funcs)
+		slave->slave_funcs->destroy(enc);
+	if (client)
+		i2c_put_adapter(client->adapter);
+
+	drm_encoder_cleanup(&slave->base);
+	kfree(slave);
+}
+
+static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
+	.destroy	= armada_drm_slave_destroy,
+};
+
+static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
+	.get_modes	= armada_drm_slave_get_modes,
+	.mode_valid	= armada_drm_slave_encoder_mode_valid,
+	.best_encoder	= armada_drm_connector_encoder,
+};
+
+static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
+	.dpms = drm_i2c_encoder_dpms,
+	.save = drm_i2c_encoder_save,
+	.restore = drm_i2c_encoder_restore,
+	.mode_fixup = drm_i2c_encoder_mode_fixup,
+	.prepare = drm_i2c_encoder_prepare,
+	.commit = drm_i2c_encoder_commit,
+	.mode_set = drm_i2c_encoder_mode_set,
+	.detect = drm_i2c_encoder_detect,
+};
+
+static int armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
+{
+	const struct armada_drm_slave_config *config = data;
+	struct drm_encoder_slave *slave;
+	struct i2c_adapter *adap;
+	int ret;
+
+	conn->interlace_allowed = config->interlace_allowed;
+	conn->doublescan_allowed = config->doublescan_allowed;
+	conn->polled = config->polled;
+
+	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
+
+	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+	if (!slave)
+		return -ENOMEM;
+
+	slave->base.possible_crtcs = config->crtcs;
+
+	adap = i2c_get_adapter(config->i2c_adapter_id);
+	if (!adap) {
+		kfree(slave);
+		return -EPROBE_DEFER;
+	}
+
+	ret = drm_encoder_init(conn->dev, &slave->base,
+			       &armada_drm_slave_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret) {
+		DRM_ERROR("unable to init encoder\n");
+		i2c_put_adapter(adap);
+		kfree(slave);
+		return ret;
+	}
+
+	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
+	i2c_put_adapter(adap);
+	if (ret) {
+		DRM_ERROR("unable to init encoder slave\n");
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
+
+	ret = slave->slave_funcs->create_resources(&slave->base, conn);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	conn->encoder = &slave->base;
+
+	return ret;
+}
+
+static const struct armada_output_type armada_drm_conn_slave = {
+	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
+	.create		= armada_drm_conn_slave_create,
+	.set_property	= armada_drm_slave_encoder_set_property,
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *config)
+{
+	return armada_output_create(dev, &armada_drm_conn_slave, config);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
new file mode 100644
index 0000000..1b86696
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_TDA19988_H
+#define ARMADA_TDA19988_H
+
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+
+struct armada_drm_slave_config {
+	int i2c_adapter_id;
+	uint32_t crtcs;
+	uint8_t polled;
+	bool interlace_allowed;
+	bool doublescan_allowed;
+	struct i2c_board_info info;
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/drm_helper.h b/drivers/gpu/drm/armada/drm_helper.h
new file mode 100644
index 0000000..d9f2e8d
--- /dev/null
+++ b/drivers/gpu/drm/armada/drm_helper.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_HELPER_H
+#define DRM_HELPER_H
+
+/* Useful helpers I wish the DRM core would provide */
+
+#include <drm/drmP.h>
+
+static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
+	return mo ? obj_to_crtc(mo) : NULL;
+}
+
+static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
+	return mo ? obj_to_encoder(mo) : NULL;
+}
+
+#endif
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 3/8] drm/i2c: nxp-tda998x: fix EDID reading on TDA19988 devices
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:30   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:30 UTC (permalink / raw)
  To: linux-arm-kernel

TDA19988 devices need their RAM enabled in order to read EDID
information.  Add support for this.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |   14 +++++++++++++-
 1 files changed, 13 insertions(+), 1 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index e68b58a..d71c408 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -229,6 +229,8 @@ struct tda998x_priv {
 
 /* Page 12h: HDCP and OTP */
 #define REG_TX3                   REG(0x12, 0x9a)     /* read/write */
+#define REG_TX4                   REG(0x12, 0x9b)     /* read/write */
+# define TX4_PD_RAM               (1 << 1)
 #define REG_TX33                  REG(0x12, 0xb8)     /* read/write */
 # define TX33_HDMI                (1 << 1)
 
@@ -673,6 +675,7 @@ read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
 static uint8_t *
 do_get_edid(struct drm_encoder *encoder)
 {
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
 	int j = 0, valid_extensions = 0;
 	uint8_t *block, *new;
 	bool print_bad_edid = drm_debug & DRM_UT_KMS;
@@ -680,6 +683,9 @@ do_get_edid(struct drm_encoder *encoder)
 	if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
 		return NULL;
 
+	if (priv->rev == TDA19988)
+		reg_clear(encoder, REG_TX4, TX4_PD_RAM);
+
 	/* base block fetch */
 	if (read_edid_block(encoder, block, 0))
 		goto fail;
@@ -689,7 +695,7 @@ do_get_edid(struct drm_encoder *encoder)
 
 	/* if there's no extensions, we're done */
 	if (block[0x7e] == 0)
-		return block;
+		goto done;
 
 	new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
 	if (!new)
@@ -716,9 +722,15 @@ do_get_edid(struct drm_encoder *encoder)
 		block = new;
 	}
 
+done:
+	if (priv->rev == TDA19988)
+		reg_set(encoder, REG_TX4, TX4_PD_RAM);
+
 	return block;
 
 fail:
+	if (priv->rev == TDA19988)
+		reg_set(encoder, REG_TX4, TX4_PD_RAM);
 	dev_warn(encoder->dev->dev, "failed to read EDID\n");
 	kfree(block);
 	return NULL;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 3/8] drm/i2c: nxp-tda998x: fix EDID reading on TDA19988 devices
@ 2013-06-09 19:30   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:30 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

TDA19988 devices need their RAM enabled in order to read EDID
information.  Add support for this.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |   14 +++++++++++++-
 1 files changed, 13 insertions(+), 1 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index e68b58a..d71c408 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -229,6 +229,8 @@ struct tda998x_priv {
 
 /* Page 12h: HDCP and OTP */
 #define REG_TX3                   REG(0x12, 0x9a)     /* read/write */
+#define REG_TX4                   REG(0x12, 0x9b)     /* read/write */
+# define TX4_PD_RAM               (1 << 1)
 #define REG_TX33                  REG(0x12, 0xb8)     /* read/write */
 # define TX33_HDMI                (1 << 1)
 
@@ -673,6 +675,7 @@ read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
 static uint8_t *
 do_get_edid(struct drm_encoder *encoder)
 {
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
 	int j = 0, valid_extensions = 0;
 	uint8_t *block, *new;
 	bool print_bad_edid = drm_debug & DRM_UT_KMS;
@@ -680,6 +683,9 @@ do_get_edid(struct drm_encoder *encoder)
 	if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
 		return NULL;
 
+	if (priv->rev == TDA19988)
+		reg_clear(encoder, REG_TX4, TX4_PD_RAM);
+
 	/* base block fetch */
 	if (read_edid_block(encoder, block, 0))
 		goto fail;
@@ -689,7 +695,7 @@ do_get_edid(struct drm_encoder *encoder)
 
 	/* if there's no extensions, we're done */
 	if (block[0x7e] == 0)
-		return block;
+		goto done;
 
 	new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
 	if (!new)
@@ -716,9 +722,15 @@ do_get_edid(struct drm_encoder *encoder)
 		block = new;
 	}
 
+done:
+	if (priv->rev == TDA19988)
+		reg_set(encoder, REG_TX4, TX4_PD_RAM);
+
 	return block;
 
 fail:
+	if (priv->rev == TDA19988)
+		reg_set(encoder, REG_TX4, TX4_PD_RAM);
 	dev_warn(encoder->dev->dev, "failed to read EDID\n");
 	kfree(block);
 	return NULL;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 4/8] drm/i2c: nxp-tda998x: ensure VIP output mux is properly set
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:31   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:31 UTC (permalink / raw)
  To: linux-arm-kernel

When switching between various drivers for this device, it's possible
that some critical registers are left containing values which affect
the device operation.  One such case encountered is the VIP output
mux register.  This defaults to 0x24 on powerup, but other drivers may
set this to 0x12.  This results in incorrect colours.

Fix this by ensuring that the register is always set to the power on
default setting.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index d71c408..dc0428d 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -110,6 +110,7 @@ struct tda998x_priv {
 #define REG_VIP_CNTRL_5           REG(0x00, 0x25)     /* write */
 # define VIP_CNTRL_5_CKCASE       (1 << 0)
 # define VIP_CNTRL_5_SP_CNT(x)    (((x) & 3) << 1)
+#define REG_MUX_VP_VIP_OUT        REG(0x00, 0x27)     /* read/write */
 #define REG_MAT_CONTRL            REG(0x00, 0x80)     /* write */
 # define MAT_CONTRL_MAT_SC(x)     (((x) & 3) << 0)
 # define MAT_CONTRL_MAT_BP        (1 << 2)
@@ -415,6 +416,9 @@ tda998x_reset(struct drm_encoder *encoder)
 	reg_write(encoder, REG_PLL_SCGR1,    0x5b);
 	reg_write(encoder, REG_PLL_SCGR2,    0x00);
 	reg_write(encoder, REG_PLL_SCG2,     0x10);
+
+	/* Ensure VP output bus muxes result in no swapping */
+	reg_write(encoder, REG_MUX_VP_VIP_OUT, 0x24);
 }
 
 /* DRM encoder functions */
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 4/8] drm/i2c: nxp-tda998x: ensure VIP output mux is properly set
@ 2013-06-09 19:31   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:31 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

When switching between various drivers for this device, it's possible
that some critical registers are left containing values which affect
the device operation.  One such case encountered is the VIP output
mux register.  This defaults to 0x24 on powerup, but other drivers may
set this to 0x12.  This results in incorrect colours.

Fix this by ensuring that the register is always set to the power on
default setting.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index d71c408..dc0428d 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -110,6 +110,7 @@ struct tda998x_priv {
 #define REG_VIP_CNTRL_5           REG(0x00, 0x25)     /* write */
 # define VIP_CNTRL_5_CKCASE       (1 << 0)
 # define VIP_CNTRL_5_SP_CNT(x)    (((x) & 3) << 1)
+#define REG_MUX_VP_VIP_OUT        REG(0x00, 0x27)     /* read/write */
 #define REG_MAT_CONTRL            REG(0x00, 0x80)     /* write */
 # define MAT_CONTRL_MAT_SC(x)     (((x) & 3) << 0)
 # define MAT_CONTRL_MAT_BP        (1 << 2)
@@ -415,6 +416,9 @@ tda998x_reset(struct drm_encoder *encoder)
 	reg_write(encoder, REG_PLL_SCGR1,    0x5b);
 	reg_write(encoder, REG_PLL_SCGR2,    0x00);
 	reg_write(encoder, REG_PLL_SCG2,     0x10);
+
+	/* Ensure VP output bus muxes result in no swapping */
+	reg_write(encoder, REG_MUX_VP_VIP_OUT, 0x24);
 }
 
 /* DRM encoder functions */
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-09 19:32   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds support for the pair of LCD controllers on the Marvell
Armada 510 SoCs.  This driver supports:
- multiple contiguous scanout buffers for video and graphics
- shm backed cacheable buffer objects for X pixmaps for Vivante GPU
  acceleration
- dual lcd0 and lcd1 crt operation
- video overlay on each LCD crt
- page flipping of the main scanout buffers

Included in this commit is the core driver with no output support; output
support is platform and encoder driver dependent.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/Kconfig                 |    2 +
 drivers/gpu/drm/Makefile                |    1 +
 drivers/gpu/drm/armada/Kconfig          |   15 +
 drivers/gpu/drm/armada/Makefile         |    6 +
 drivers/gpu/drm/armada/armada_crtc.c    |  766 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/armada/armada_crtc.h    |   75 +++
 drivers/gpu/drm/armada/armada_debugfs.c |  186 ++++++++
 drivers/gpu/drm/armada/armada_drm.h     |   62 +++
 drivers/gpu/drm/armada/armada_drv.c     |  326 +++++++++++++
 drivers/gpu/drm/armada/armada_fb.c      |  156 +++++++
 drivers/gpu/drm/armada/armada_fb.h      |   21 +
 drivers/gpu/drm/armada/armada_fbdev.c   |  210 +++++++++
 drivers/gpu/drm/armada/armada_gem.c     |  420 +++++++++++++++++
 drivers/gpu/drm/armada/armada_gem.h     |   41 ++
 drivers/gpu/drm/armada/armada_hw.h      |  295 ++++++++++++
 drivers/gpu/drm/armada/armada_ioctl.h   |  128 +++++
 drivers/gpu/drm/armada/armada_ioctlP.h  |   22 +
 drivers/gpu/drm/armada/armada_output.c  |  159 +++++++
 drivers/gpu/drm/armada/armada_output.h  |   38 ++
 drivers/gpu/drm/armada/armada_overlay.c |  514 +++++++++++++++++++++
 drivers/gpu/drm/armada/armada_slave.c   |  138 ++++++
 drivers/gpu/drm/armada/armada_slave.h   |   26 +
 drivers/gpu/drm/armada/drm_helper.h     |   31 ++
 23 files changed, 3638 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/armada/Kconfig
 create mode 100644 drivers/gpu/drm/armada/Makefile
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.c
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.h
 create mode 100644 drivers/gpu/drm/armada/armada_debugfs.c
 create mode 100644 drivers/gpu/drm/armada/armada_drm.h
 create mode 100644 drivers/gpu/drm/armada/armada_drv.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.h
 create mode 100644 drivers/gpu/drm/armada/armada_fbdev.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.h
 create mode 100644 drivers/gpu/drm/armada/armada_hw.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctl.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctlP.h
 create mode 100644 drivers/gpu/drm/armada/armada_output.c
 create mode 100644 drivers/gpu/drm/armada/armada_output.h
 create mode 100644 drivers/gpu/drm/armada/armada_overlay.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.h
 create mode 100644 drivers/gpu/drm/armada/drm_helper.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 1e82882..ae8a57f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
 
 source "drivers/gpu/drm/cirrus/Kconfig"
 
+source "drivers/gpu/drm/armada/Kconfig"
+
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d59b24..b458168 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
+obj-$(CONFIG_DRM_ARMADA) += armada/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-$(CONFIG_DRM_OMAP)	+= omapdrm/
diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
new file mode 100644
index 0000000..c7a0a94
--- /dev/null
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -0,0 +1,15 @@
+config DRM_ARMADA
+	tristate "DRM support for Marvell Armada SoCs"
+	depends on DRM && HAVE_CLK
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select DRM_KMS_HELPER
+	help
+	  Support the "LCD" controllers found on the Marvell Armada 510
+	  devices.  There are two controllers on the device, each controller
+	  supports graphics and video overlays.
+
+	  This driver provides no built-in acceleration; acceleration is
+	  performed by other IP found on the SoC.  This driver provides
+	  kernel mode setting and buffer management to userspace.
diff --git a/drivers/gpu/drm/armada/Makefile b/drivers/gpu/drm/armada/Makefile
new file mode 100644
index 0000000..430f025
--- /dev/null
+++ b/drivers/gpu/drm/armada/Makefile
@@ -0,0 +1,6 @@
+armada-y	:= armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
+		   armada_gem.o armada_output.o armada_overlay.o \
+		   armada_slave.o
+armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o
+
+obj-$(CONFIG_DRM_ARMADA) := armada.o
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
new file mode 100644
index 0000000..e5ab4cb
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -0,0 +1,766 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+
+struct armada_frame_work {
+	struct drm_pending_vblank_event *event;
+	struct armada_regs regs[4];
+	struct drm_gem_object *old_fb_obj;
+	struct work_struct work;
+};
+
+/*
+ * A note about interlacing.  Let's consider HDMI 1920x1080i.
+ * The timing parameters we have from X are:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640  1080 1084 1094 1125
+ * Which get translated to:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640   540  542  547  562
+ *
+ * This is how it is defined by CEA-861-D - line and pixel numbers are
+ * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
+ * line: 2640.  The odd frame, the first active line is at line 21, and
+ * the even frame, the first active line is 584.
+ *
+ * LN:    560     561     562     563             567     568    569
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
+ *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
+ *
+ * LN:    1123   1124    1125      1               5       6      7
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
+ *  23 blanking lines
+ *
+ * The Armada LCD Controller line and pixel numbers are, like X timings,
+ * referenced to the top left of the active frame.
+ *
+ * So, translating these to our LCD controller:
+ *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
+ *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
+ * Note: Vsync front porch remains constant!
+ *
+ * if (odd_frame) {
+ *   vtotal = mode->crtc_vtotal + 1;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
+ *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
+ * } else {
+ *   vtotal = mode->crtc_vtotal;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
+ *   vhorizpos = mode->crtc_hsync_start;
+ * }
+ * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
+ *
+ * So, we need to reprogram these registers on each vsync event:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *
+ * Note: we do not use the frame done interrupts because these appear
+ * to happen too early, and lead to jitter on the display (presumably
+ * they occur at the end of the last active line, before the vsync back
+ * porch, which we're reprogramming.)
+ */
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+void armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs)
+{
+	while (regs->offset != ~0) {
+		void __iomem *reg = dcrtc->base + regs->offset;
+		uint32_t val;
+
+		val = regs->mask;
+		if (val != 0)
+			val &= readl_relaxed(reg);
+		writel_relaxed(val | regs->val, reg);
+		++regs;
+	}
+}
+
+#define dpms_blanked(dpms)	((dpms) != DRM_MODE_DPMS_ON)
+
+static void armada_drm_crtc_update(struct armada_crtc *dcrtc)
+{
+	uint32_t dumb_ctrl;
+
+	dumb_ctrl = dcrtc->cfg_dumb_ctrl;
+
+	if (!dpms_blanked(dcrtc->dpms))
+		dumb_ctrl |= CFG_DUMB_ENA;
+
+	/*
+	 * When a dumb interface isn't under 24bit, it might be
+	 * under SPI or GPIO.  If set to 7, this will force
+	 * LCD_D[23:0] to output blank color and damage GPIO
+	 * and SPI behaviour.  So leave it as-is unless in
+	 * DUMB24_RGB888_0 mode.
+	 */
+	if (dpms_blanked(dcrtc->dpms) &&
+	    (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
+		dumb_ctrl &= ~DUMB_MASK;
+		dumb_ctrl |= DUMB_BLANK;
+	}
+
+	/*
+	 * The spec is unclear about the polarities of the syncs.
+	 * We assume their non-inverted state is active high.
+	 */
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC)
+		dumb_ctrl |= CFG_INV_CSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC)
+		dumb_ctrl |= CFG_INV_HSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC)
+		dumb_ctrl |= CFG_INV_VSYNC;
+
+	if (dcrtc->dumb_ctrl != dumb_ctrl) {
+		dcrtc->dumb_ctrl = dumb_ctrl;
+		writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+}
+
+static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb, int x, int y,
+	struct armada_regs *regs, bool interlaced)
+{
+	struct armada_gem_object *obj = drm_fb_obj(fb);
+	unsigned pitch = fb->pitches[0];
+	unsigned offset = y * pitch + x * fb->bits_per_pixel / 8;
+	uint32_t addr_odd, addr_even;
+	unsigned i = 0;
+
+	DRM_DEBUG_DRIVER("pitch %u x %d y %d bpp %d\n",
+		pitch, x, y, fb->bits_per_pixel);
+
+	addr_odd = addr_even = obj->dev_addr + offset;
+
+	if (interlaced) {
+		addr_even += pitch;
+		pitch *= 2;
+	}
+
+	/* write offset, base, and pitch */
+	armada_reg_queue_set(regs, i, addr_odd, LCD_CFG_GRA_START_ADDR0);
+	armada_reg_queue_set(regs, i, addr_even, LCD_CFG_GRA_START_ADDR1);
+	armada_reg_queue_mod(regs, i, pitch, 0xffff, LCD_CFG_GRA_PITCH);
+
+	return i;
+}
+
+static void armada_drm_unref_work(struct work_struct *_work)
+{
+	struct armada_frame_work *work =
+		container_of(_work, struct armada_frame_work, work);
+	drm_gem_object_unreference_unlocked(work->old_fb_obj);
+	kfree(work);
+}
+
+static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
+	struct armada_frame_work *work)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	unsigned long flags;
+	int ret;
+
+	INIT_WORK(&work->work, armada_drm_unref_work);
+
+	ret = drm_vblank_get(dev, dcrtc->num);
+	if (ret) {
+		DRM_ERROR("failed to acquire vblank counter\n");
+		return ret;
+	}
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (!dcrtc->frame_work) {
+		dcrtc->frame_work = work;
+	} else {
+		ret = -EBUSY;
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+
+	if (ret)
+		drm_vblank_put(dev, dcrtc->num);
+
+	return ret;
+}
+
+static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	struct armada_frame_work *work = dcrtc->frame_work;
+
+	dcrtc->frame_work = NULL;
+
+	armada_drm_crtc_update_regs(dcrtc, work->regs);
+
+	if (work->event)
+		drm_send_vblank_event(dev, dcrtc->num, work->event);
+
+	drm_vblank_put(dev, dcrtc->num);
+
+	/* Finally, queue the process-half of the cleanup */
+	schedule_work(&work->work);
+}
+
+static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
+	struct drm_framebuffer *fb, bool force)
+{
+	struct armada_gem_object *obj;
+	struct armada_frame_work *work;
+
+	if (!fb)
+		return;
+
+	obj = drm_fb_obj(fb);
+	if (force) {
+		/* Display is disabled, so just drop the old fb */
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return;
+	}
+
+	work = kmalloc(sizeof *work, GFP_KERNEL);
+	if (work) {
+		int i = 0;
+		work->event = NULL;
+		work->old_fb_obj = &obj->obj;
+		armada_reg_queue_end(work->regs, i);
+
+		if (armada_drm_crtc_queue_frame_work(dcrtc, work) == 0)
+			return;
+
+		kfree(work);
+	}
+
+	/*
+	 * Oops - just drop the reference immediately and hope for
+	 * the best.  The worst that will happen is the buffer gets
+	 * reused before it has finished being displayed.
+	 */
+	drm_gem_object_unreference_unlocked(&obj->obj);
+}
+
+static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+
+	/*
+	 * Tell the DRM core that vblank IRQs aren't going to happen for
+	 * a while.  This cleans up any pending vblank events for us.
+	 */
+	drm_vblank_off(dev, dcrtc->num);
+
+	/* Handle any pending flip event. */
+	spin_lock_irq(&dev->event_lock);
+	if (dcrtc->frame_work)
+		armada_drm_crtc_complete_frame_work(dcrtc);
+	spin_unlock_irq(&dev->event_lock);
+}
+
+void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b, int idx)
+{
+}
+
+void armada_drm_crtc_gamma_get(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, int idx)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_dpms(struct drm_crtc *crtc, int dpms)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != dpms) {
+		dcrtc->dpms = dpms;
+		armada_drm_crtc_update(dcrtc);
+		if (dpms_blanked(dpms))
+			armada_drm_vblank_off(dcrtc);
+	}
+}
+
+/*
+ * Prepare for a mode set.  Turn off overlay to ensure that we don't end
+ * up with the overlay size being bigger than the active screen size.
+ * We rely upon X refreshing this state after the mode set has completed.
+ *
+ * The mode_config.mutex will be held for this call
+ */
+static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	mutex_lock(&dev->struct_mutex);
+	if (dcrtc->overlay)
+		armada_drm_overlay_off(dev, dcrtc->overlay);
+	mutex_unlock(&dev->struct_mutex);
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_commit(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_ON) {
+		dcrtc->dpms = DRM_MODE_DPMS_ON;
+		armada_drm_crtc_update(dcrtc);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj)
+{
+	return true;
+}
+
+void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
+{
+	struct armada_vbl_event *e, *n;
+	void __iomem *base = dcrtc->base;
+
+	if (stat & DMA_FF_UNDERFLOW)
+		DRM_ERROR("video underflow on crtc %u\n", dcrtc->num);
+	if (stat & GRA_FF_UNDERFLOW)
+		DRM_ERROR("graphics underflow on crtc %u\n", dcrtc->num);
+
+	if (stat & VSYNC_IRQ)
+		drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num);
+
+	spin_lock(&dcrtc->irq_lock);
+
+	list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) {
+		list_del_init(&e->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		e->fn(dcrtc, e->data);
+	}
+
+	if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) {
+		int i = stat & GRA_FRAME_IRQ0 ? 0 : 1;
+		uint32_t val;
+
+		writel_relaxed(dcrtc->v[i].spu_v_porch, base + LCD_SPU_V_PORCH);
+		writel_relaxed(dcrtc->v[i].spu_v_h_total, base + LCD_SPUT_V_H_TOTAL);
+
+		val = readl_relaxed(base + LCD_SPU_ADV_REG);
+		val &= ~(0xfff << 20 | 0xfff);
+		val |= dcrtc->v[i].spu_adv_reg;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG);
+	}
+	spin_unlock(&dcrtc->irq_lock);
+
+	/* Only on frame 0 IRQs (start of progressive / odd frame) */
+	if (stat & GRA_FRAME_IRQ0) {
+		struct drm_device *dev = dcrtc->crtc.dev;
+
+		spin_lock(&dev->event_lock);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock(&dev->event_lock);
+
+		wake_up(&dcrtc->frame_wait);
+	}
+}
+
+/* These are locked by dev->vbl_lock */
+void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if (dcrtc->irq_ena & mask) {
+		dcrtc->irq_ena &= ~mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	}
+}
+
+void armada_drm_crtc_enable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if ((dcrtc->irq_ena & mask) != mask) {
+		dcrtc->irq_ena |= mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+		if (readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR) & mask)
+			writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
+	struct drm_display_mode *mode, struct drm_display_mode *adj,
+	int x, int y, struct drm_framebuffer *old_fb)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[16];
+	uint32_t lm, rm, tm, bm, val, rate, ref, div;
+	unsigned long flags;
+	unsigned i;
+	bool interlaced;
+
+	drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
+
+	interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
+
+	i = armada_drm_crtc_calc_fb(dcrtc->crtc.fb, x, y, regs, interlaced);
+
+	rm = adj->crtc_hsync_start - adj->crtc_hdisplay;
+	lm = adj->crtc_htotal - adj->crtc_hsync_end;
+	bm = adj->crtc_vsync_start - adj->crtc_vdisplay;
+	tm = adj->crtc_vtotal - adj->crtc_vsync_end;
+
+	DRM_DEBUG_DRIVER("H: %d %d %d %d lm %d rm %d\n",
+		adj->crtc_hdisplay,
+		adj->crtc_hsync_start,
+		adj->crtc_hsync_end,
+		adj->crtc_htotal, lm, rm);
+	DRM_DEBUG_DRIVER("V: %d %d %d %d tm %d bm %d\n",
+		adj->crtc_vdisplay,
+		adj->crtc_vsync_start,
+		adj->crtc_vsync_end,
+		adj->crtc_vtotal, tm, bm);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	drm_vblank_pre_modeset(crtc->dev, dcrtc->num);
+
+	crtc->mode = *adj;
+
+	val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA;
+	if (val != dcrtc->dumb_ctrl) {
+		dcrtc->dumb_ctrl = val;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+
+	rate = adj->clock * 1000;
+	clk_set_rate(dcrtc->clk, rate);
+	ref = clk_get_rate(dcrtc->clk);
+	div = DIV_ROUND_UP(ref, rate);
+	if (div < 1)
+		div = 1;
+	armada_reg_queue_set(regs, i, div | dcrtc->cfg_sclk, LCD_CFG_SCLK_DIV);
+
+	if (interlaced ^ dcrtc->interlaced) {
+		if (adj->flags & DRM_MODE_FLAG_INTERLACE)
+			drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+		else
+			drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		dcrtc->interlaced = interlaced;
+	}
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+
+	/* Even interlaced/progressive frame */
+	dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 |
+				    adj->crtc_htotal;
+	dcrtc->v[1].spu_v_porch = tm << 16 | bm;
+	val = adj->crtc_hsync_start;
+	dcrtc->v[1].spu_adv_reg = val << 20 | val;
+
+	if (interlaced) {
+		/* Odd interlaced frame */
+		dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total +
+						(1 << 16);
+		dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1;
+		val = adj->crtc_hsync_start - adj->crtc_htotal / 2;
+		dcrtc->v[0].spu_adv_reg = val << 20 | val;
+	} else {
+		dcrtc->v[0] = dcrtc->v[1];
+	}
+
+	val = (adj->crtc_vdisplay << 16) | adj->crtc_hdisplay;
+
+	armada_reg_queue_set(regs, i, val, LCD_SPU_V_H_ACTIVE);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GRA_HPXL_VLN);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GZM_HPXL_VLN);
+	armada_reg_queue_set(regs, i, (lm << 16) | rm, LCD_SPU_H_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_porch, LCD_SPU_V_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total,
+			   LCD_SPUT_V_H_TOTAL);
+	armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg | ADV_VSYNCOFFEN,
+			   (0xfff << 20 | 0xfff), LCD_SPU_ADV_REG);
+
+	val = dcrtc->cfg_dma_ctrl0;
+
+	switch (crtc->fb->pixel_format) {
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+		val |= CFG_GRA_1555;
+		break;
+	case DRM_FORMAT_RGB565:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR565:
+		val |= CFG_GRA_565;
+		break;
+	case DRM_FORMAT_RGB888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR888:
+		val |= CFG_GRA_888PACK;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR8888:
+		val |= CFG_GRA_X888;
+		break;
+	case DRM_FORMAT_ARGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_ABGR8888:
+		val |= CFG_GRA_8888;
+		break;
+	case DRM_FORMAT_C8:
+		val |= CFG_GRA_PSEUDO8 | CFG_PALETTE_ENA;
+		break;
+	}
+	if (interlaced)
+		val |= CFG_GRA_FTOGGLE;
+	armada_reg_queue_mod(regs, i, val, CFG_GRAFORMAT | CFG_GRA_SWAPRB |
+			   CFG_PALETTE_ENA | CFG_GRA_FTOGGLE,
+			   LCD_SPU_DMA_CTRL0);
+
+	val = adj->flags & DRM_MODE_FLAG_NVSYNC ? CFG_VSYNC_INV : 0;
+	armada_reg_queue_mod(regs, i, val, CFG_VSYNC_INV, LCD_SPU_DMA_CTRL1);
+
+	/*
+	 * Set the colorimetry, based upon the HDMI spec.
+	 * 1280x720p, 1920x1080p and 1920x1080i use ITU709, others
+	 * use ITU601.  In any case, we select professional.
+	 */
+	if ((adj->hdisplay == 1280 && adj->vdisplay == 720 && !interlaced) ||
+	    (adj->hdisplay == 1920 && adj->vdisplay == 1080)) {
+		val = CFG_CSC_PROF | CFG_CSC_CCIR709;
+	} else {
+		val = CFG_CSC_PROF;
+	}
+	armada_reg_queue_mod(regs, i, val, CFG_CSC_MASK, LCD_SPU_IOPAD_CONTROL);
+	armada_reg_queue_end(regs, i);
+
+	armada_drm_crtc_update_regs(dcrtc, regs);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+
+	armada_drm_crtc_update(dcrtc);
+
+	drm_vblank_post_modeset(crtc->dev, dcrtc->num);
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+	struct drm_framebuffer *old_fb)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[4];
+	unsigned i;
+
+	i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
+	armada_reg_queue_end(regs, i);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	/* Take a reference to the new fb as we're using it */
+	drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
+
+	/* Update the base in the CRTC */
+	armada_drm_crtc_update_regs(dcrtc, regs);
+
+	/* Drop our previously held reference */
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+static void armada_drm_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_disable(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_OFF) {
+		dcrtc->dpms = DRM_MODE_DPMS_OFF;
+		armada_drm_crtc_update(dcrtc);
+	}
+	armada_drm_vblank_off(dcrtc);
+	armada_drm_crtc_finish_fb(dcrtc, crtc->fb, true);
+}
+
+static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
+	.dpms		= armada_drm_crtc_dpms,
+	.prepare	= armada_drm_crtc_prepare,
+	.commit		= armada_drm_crtc_commit,
+	.mode_fixup	= armada_drm_crtc_mode_fixup,
+	.mode_set	= armada_drm_crtc_mode_set,
+	.mode_set_base	= armada_drm_crtc_mode_set_base,
+	.load_lut	= armada_drm_crtc_load_lut,
+	.disable	= armada_drm_crtc_disable,
+};
+
+static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_private *priv = crtc->dev->dev_private;
+
+	priv->dcrtc[dcrtc->num] = NULL;
+	drm_crtc_cleanup(&dcrtc->crtc);
+	clk_disable_unprepare(dcrtc->clk);
+	clk_put(dcrtc->clk);
+	kfree(dcrtc);
+}
+
+/*
+ * The mode_config lock is held here, to prevent races between this
+ * and a mode_set.
+ */
+static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
+	struct drm_framebuffer *fb, struct drm_pending_vblank_event *event)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_frame_work *work;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+	unsigned i;
+	int ret;
+
+	/* We don't support changing the pixel format */
+	if (fb->pixel_format != crtc->fb->pixel_format)
+		return -EINVAL;
+
+	work = kmalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return -ENOMEM;
+
+	work->event = event;
+	work->old_fb_obj = &drm_fb_obj(dcrtc->crtc.fb)->obj;
+
+	i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs, dcrtc->interlaced);
+	armada_reg_queue_end(work->regs, i);
+
+	/*
+	 * Hold the old framebuffer for the work - DRM appears to drop our
+	 * reference to the old framebuffer in drm_mode_page_flip_ioctl().
+	 */
+	drm_gem_object_reference(work->old_fb_obj);
+
+	ret = armada_drm_crtc_queue_frame_work(dcrtc, work);
+	if (ret) {
+		/*
+		 * Undo our reference above; DRM does not drop the reference
+		 * to this object on error, so that's okay.
+		 */
+		drm_gem_object_unreference(work->old_fb_obj);
+		kfree(work);
+		return ret;
+	}
+
+	/*
+	 * Don't take a reference on the new framebuffer;
+	 * drm_mode_page_flip_ioctl() has already grabbed a reference and
+	 * will _not_ drop that reference on successful return from this
+	 * function.  Simply mark this new framebuffer as the current one.
+	 */
+	dcrtc->crtc.fb = fb;
+
+	/*
+	 * Finally, if the display is blanked, we won't receive an
+	 * interrupt, so complete it now.
+	 */
+	if (dpms_blanked(dcrtc->dpms)) {
+		spin_lock_irqsave(&dev->event_lock, flags);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+
+	return 0;
+}
+
+static struct drm_crtc_funcs armada_crtc_funcs = {
+	.destroy	= armada_drm_crtc_destroy,
+	.page_flip	= armada_drm_crtc_page_flip,
+	.set_config	= drm_crtc_helper_set_config,
+};
+
+int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
+	struct resource *res, struct clk *clk)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc;
+	void __iomem *base;
+	int ret;
+
+	ret = clk_prepare_enable(clk);
+	if (ret) {
+		DRM_ERROR("clk would not prepare and enable\n");
+		return ret;
+	}
+
+	base = devm_request_and_ioremap(dev->dev, res);
+	if (!base) {
+		DRM_ERROR("failed to ioremap register\n");
+		clk_disable_unprepare(clk);
+		return -ENOMEM;
+	}
+
+	dcrtc = kzalloc(sizeof(*dcrtc), GFP_KERNEL);
+	if (!dcrtc) {
+		DRM_ERROR("failed to allocate Armada crtc\n");
+		clk_disable_unprepare(clk);
+		return -ENOMEM;
+	}
+
+	dcrtc->base = base;
+	dcrtc->num = num;
+	dcrtc->clk = clk;
+	dcrtc->cfg_sclk = 0xc0000000;
+	dcrtc->cfg_dma_ctrl0 = CFG_GRA_ENA | CFG_GRA_HSMOOTH;
+	dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0;
+	spin_lock_init(&dcrtc->irq_lock);
+	dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR;
+	INIT_LIST_HEAD(&dcrtc->vbl_list);
+	init_waitqueue_head(&dcrtc->frame_wait);
+
+	/* Initialize some registers which we don't otherwise set */
+	writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_BLANKCOLOR);
+	writel_relaxed(CFG_IOPAD_DUMB24, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_SRAM_PARA0);
+	writel_relaxed(0x0000e000, dcrtc->base + LCD_SPU_SRAM_PARA1);
+	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
+
+	/* Lower the watermark so to eliminate jitter at higher bandwidths */
+	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
+
+	/* Ensure AXI pipeline is enabled */
+	armada_updatel(CFG_ARBFAST_ENA, 0, dcrtc->base + LCD_SPU_DMA_CTRL0);
+
+	priv->dcrtc[dcrtc->num] = dcrtc;
+
+	drm_crtc_init(dev, &dcrtc->crtc, &armada_crtc_funcs);
+	drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
new file mode 100644
index 0000000..53d1efe
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CRTC_H
+#define ARMADA_CRTC_H
+
+struct armada_overlay;
+struct armada_gem_object;
+
+struct armada_regs {
+	uint32_t offset;
+	uint32_t mask;
+	uint32_t val;
+};
+
+#define armada_reg_queue_mod(_r, _i, _v, _m, _o)	\
+	do {					\
+		struct armada_regs *__reg = _r;	\
+		__reg[_i].offset = _o;		\
+		__reg[_i].mask = ~(_m);		\
+		__reg[_i].val = _v;		\
+		_i++;				\
+	} while (0)
+
+#define armada_reg_queue_set(_r, _i, _v, _o)	\
+	armada_reg_queue_mod(_r, _i, _v, ~0, _o)
+
+#define armada_reg_queue_end(_r, _i)		\
+	armada_reg_queue_mod(_r, _i, 0, 0, ~0)
+
+struct armada_frame_work;
+
+struct armada_crtc {
+	struct drm_crtc		crtc;
+	unsigned		num;
+	void __iomem		*base;
+	struct clk		*clk;
+	struct {
+		uint32_t	spu_v_h_total;
+		uint32_t	spu_v_porch;
+		uint32_t	spu_adv_reg;
+	} v[2];
+	bool			interlaced;
+
+	struct armada_overlay	*overlay;
+
+	int			dpms;
+	uint32_t		cfg_sclk;
+	uint32_t		cfg_dma_ctrl0;
+	uint32_t		cfg_dumb_ctrl;
+	uint32_t		dumb_ctrl;
+
+	wait_queue_head_t	frame_wait;
+	struct armada_frame_work	*frame_work;
+
+	spinlock_t		irq_lock;
+	uint32_t		irq_ena;
+	struct list_head	vbl_list;
+};
+#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)
+
+int armada_drm_crtc_create(struct drm_device *, unsigned, struct resource *,
+	struct clk *);
+void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
+void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
+void armada_drm_crtc_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_debugfs.c b/drivers/gpu/drm/armada/armada_debugfs.c
new file mode 100644
index 0000000..ff5ada3
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_debugfs.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <drm/drmP.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+
+static int armada_debugfs_gem_offs_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct drm_gem_mm *mm = dev->mm_private;
+
+	return drm_mm_dump_table(m, &mm->offset_manager);
+}
+
+static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct armada_private *priv = node->minor->dev->dev_private;
+
+	return drm_mm_dump_table(m, &priv->linear);
+}
+
+static int armada_debugfs_reg_show(struct seq_file *m, void *data)
+{
+	struct drm_device *dev = m->private;
+	struct armada_private *priv = dev->dev_private;
+	int n, i;
+
+	if (priv) {
+		for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+			struct armada_crtc *dcrtc = priv->dcrtc[n];
+			if (!dcrtc)
+				continue;
+
+			for (i = 0x84; i <= 0x1c4; i += 4) {
+				uint32_t v = readl_relaxed(dcrtc->base + i);
+				seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, armada_debugfs_reg_show, inode->i_private);
+}
+
+static const struct file_operations fops_reg_r = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_r_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int armada_debugfs_write(struct file *file, const char __user *ptr,
+	size_t len, loff_t *off)
+{
+	struct drm_device *dev = file->private_data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	char buf[32], *p;
+	uint32_t reg, val;
+	int ret;
+
+	if (*off != 0)
+		return 0;
+
+	if (len > sizeof(buf) - 1)
+		len = sizeof(buf) - 1;
+
+	ret = strncpy_from_user(buf, ptr, len);
+	if (ret < 0)
+		return ret;
+	buf[len] = '\0';
+
+	reg = simple_strtoul(buf, &p, 16);
+	if (!isspace(*p))
+		return -EINVAL;
+	val = simple_strtoul(p + 1, NULL, 16);
+
+	if (reg >= 0x84 && reg <= 0x1c4)
+		writel(val, dcrtc->base + reg);
+
+	return len;
+}
+
+static int armada_debugfs_reg_w_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static const struct file_operations fops_reg_w = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_w_open,
+	.write = armada_debugfs_write,
+	.llseek = noop_llseek,
+};
+
+static struct drm_info_list armada_debugfs_list[] = {
+	{ "gem_linear", armada_debugfs_gem_linear_show, 0 },
+	{ "gem_offset", armada_debugfs_gem_offs_show, 0 },
+};
+#define ARMADA_DEBUGFS_ENTRIES ARRAY_SIZE(armada_debugfs_list)
+
+static int drm_add_fake_info_node(struct drm_minor *minor, struct dentry *ent,
+	const void *key)
+{
+	struct drm_info_node *node;
+
+	node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL);
+	if (node == NULL) {
+		debugfs_remove(ent);
+		return -ENOMEM;
+	}
+
+	node->minor = minor;
+	node->dent = ent;
+	node->info_ent = (void *) key;
+
+	mutex_lock(&minor->debugfs_lock);
+	list_add(&node->list, &minor->debugfs_list);
+	mutex_unlock(&minor->debugfs_lock);
+
+	return 0;
+}
+
+static int armada_debugfs_create(struct dentry *root, struct drm_minor *minor,
+	const char *name, umode_t mode, const struct file_operations *fops)
+{
+	struct dentry *de;
+
+	de = debugfs_create_file(name, mode, root, minor->dev, fops);
+
+	return drm_add_fake_info_node(minor, de, fops);
+}
+
+int armada_drm_debugfs_init(struct drm_minor *minor)
+{
+	int ret;
+
+	ret = drm_debugfs_create_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				       minor->debugfs_root, minor);
+	if (ret)
+		return ret;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				   "reg", S_IFREG | S_IRUSR, &fops_reg_r);
+	if (ret)
+		goto err_1;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				"reg_wr", S_IFREG | S_IWUSR, &fops_reg_w);
+	if (ret)
+		goto err_2;
+	return ret;
+
+ err_2:
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+ err_1:
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+	return ret;
+}
+
+void armada_drm_debugfs_cleanup(struct drm_minor *minor)
+{
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_w, 1, minor);
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+}
diff --git a/drivers/gpu/drm/armada/armada_drm.h b/drivers/gpu/drm/armada/armada_drm.h
new file mode 100644
index 0000000..c73e29b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drm.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_DRM_H
+#define ARMADA_DRM_H
+
+struct armada_crtc;
+struct armada_gem_object;
+struct armada_overlay;
+struct clk;
+
+static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp)
+{
+	uint32_t pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2;
+
+	/* 88AP510 spec recommends pitch be a multiple of 128 */
+	return ALIGN(pitch, 128);
+}
+
+struct armada_vbl_event {
+	struct list_head	node;
+	void			*data;
+	void			(*fn)(struct armada_crtc *, void *);
+};
+void armada_drm_vbl_event_add(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *, struct armada_vbl_event *);
+#define armada_drm_vbl_event_init(_e,_f,_d) do {	\
+	struct armada_vbl_event *__e = _e;	\
+	INIT_LIST_HEAD(&__e->node);		\
+	__e->data = _d;				\
+	__e->fn = _f;				\
+} while (0)
+
+
+struct armada_private {
+	struct drm_fb_helper	*fbdev;
+	struct armada_crtc	*dcrtc[2];
+	struct armada_overlay	*overlay;
+	struct drm_mm		linear;
+#ifdef CONFIG_DEBUG_FS
+	struct dentry		*de;
+#endif
+};
+
+extern const struct drm_mode_config_funcs armada_drm_mode_config_funcs;
+
+int armada_fbdev_init(struct drm_device *);
+void armada_fbdev_fini(struct drm_device *);
+
+int armada_overlay_create(struct drm_device *);
+void armada_overlay_destroy(struct drm_device *);
+void armada_drm_overlay_off(struct drm_device *, struct armada_overlay *);
+
+int armada_drm_debugfs_init(struct drm_minor *);
+void armada_drm_debugfs_cleanup(struct drm_minor *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
new file mode 100644
index 0000000..bb70cf5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+static int armada_drm_load(struct drm_device *dev, unsigned long flags)
+{
+	struct armada_private *priv;
+	struct resource *res[ARRAY_SIZE(priv->dcrtc)];
+	struct resource *mem = NULL;
+	int ret, n, i;
+
+	memset(res, 0, sizeof(res));
+
+	for (n = i = 0; ; n++) {
+		struct resource *r = platform_get_resource(dev->platformdev,
+							   IORESOURCE_MEM, n);
+		if (!r)
+			break;
+
+		if (resource_size(r) > SZ_4K)
+			mem = r;
+		else if (i < ARRAY_SIZE(priv->dcrtc))
+			res[i++] = r;
+		else
+			return -EINVAL;
+	}
+
+	if (!res[0] || !mem)
+		return -ENXIO;
+
+	if (!devm_request_mem_region(dev->dev, mem->start,
+			resource_size(mem), "armada-drm"))
+		return -EBUSY;
+
+	priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		DRM_ERROR("failed to allocate private\n");
+		ret = -ENOMEM;
+		goto err_buf;
+	}
+
+	dev->dev_private = priv;
+
+	/* Mode setting support */
+	drm_mode_config_init(dev);
+	dev->mode_config.min_width = 0;
+	dev->mode_config.min_height = 0;
+	dev->mode_config.max_width = 2048;
+	dev->mode_config.max_height = 2048;
+	dev->mode_config.preferred_depth = 24;
+	dev->mode_config.funcs = &armada_drm_mode_config_funcs;
+	drm_mm_init(&priv->linear, mem->start, resource_size(mem));
+
+	/* Create all LCD controllers */
+	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+		struct clk *clk;
+
+		if (!res[n])
+			break;
+
+		clk = clk_get_sys("dovefb.0", "extclk");
+		if (IS_ERR(clk)) {
+			DRM_ERROR("failed to get clock\n");
+			ret = PTR_ERR(clk);
+			if (ret == -ENOENT)
+				ret = -EPROBE_DEFER;
+			goto err_kms;
+		}
+
+		ret = armada_drm_crtc_create(dev, n, res[n], clk);
+		if (ret) {
+			clk_put(clk);
+			goto err_kms;
+		}
+	}
+
+	ret = drm_vblank_init(dev, n);
+	if (ret)
+		goto err_kms;
+
+	ret = drm_irq_install(dev);
+	if (ret)
+		goto err_kms;
+
+	dev->vblank_disable_allowed = 1;
+
+	ret = armada_fbdev_init(dev);
+	if (ret)
+		goto err_irq;
+
+	ret = armada_overlay_create(dev);
+	if (ret)
+		goto err_irq;
+
+	drm_kms_helper_poll_init(dev);
+
+	return 0;
+
+ err_irq:
+	drm_irq_uninstall(dev);
+ err_kms:
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+ err_buf:
+
+	return ret;
+}
+
+static int armada_drm_unload(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+
+	drm_kms_helper_poll_fini(dev);
+	armada_overlay_destroy(dev);
+	armada_fbdev_fini(dev);
+	drm_irq_uninstall(dev);
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+	dev->dev_private = NULL;
+
+	return 0;
+}
+
+void armada_drm_vbl_event_add(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	if (list_empty(&evt->node)) {
+		list_add_tail(&evt->node, &dcrtc->vbl_list);
+
+		drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+	}
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+void armada_drm_vbl_event_remove(struct armada_crtc *dcrtc,
+	struct armada_vbl_event *evt)
+{
+	if (!list_empty(&evt->node)) {
+		list_del_init(&evt->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+	}
+}
+
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	armada_drm_vbl_event_remove(dcrtc, evt);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+/* These are called under the vbl_lock. */
+static int armada_drm_enable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_enable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+	return 0;
+}
+
+static void armada_drm_disable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_disable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+}
+
+static irqreturn_t armada_drm_irq_handler(int irq, void *arg)
+{
+	struct drm_device *dev = arg;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	uint32_t v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);
+	irqreturn_t handled = IRQ_NONE;
+
+	/*
+	 * This is rediculous - rather than writing bits to clear, we
+	 * have to set the actual status register value.  This is racy.
+	 */
+	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+
+	/* Mask out those interrupts we haven't enabled */
+	v = stat & dcrtc->irq_ena;
+
+	if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) {
+		armada_drm_crtc_irq(dcrtc, stat);
+		handled = IRQ_HANDLED;
+	}
+
+	return handled;
+}
+
+static int armada_drm_irq_postinstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	spin_lock_irq(&dev->vbl_lock);
+	writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	spin_unlock_irq(&dev->vbl_lock);
+
+	return 0;
+}
+
+static void armada_drm_irq_uninstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ENA);
+}
+
+static struct drm_ioctl_desc armada_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_MMAP, armada_gem_mmap_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PROP, armada_gem_prop_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PWRITE, armada_gem_pwrite_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_PUT_IMAGE, armada_overlay_put_image_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_ATTRS, armada_overlay_attrs_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+};
+
+static const struct file_operations armada_drm_fops = {
+	.owner			= THIS_MODULE,
+	.llseek			= no_llseek,
+	.read			= drm_read,
+	.poll			= drm_poll,
+	.unlocked_ioctl		= drm_ioctl,
+	.mmap			= drm_gem_mmap,
+	.open			= drm_open,
+	.release		= drm_release,
+	.fasync			= drm_fasync,
+};
+
+extern const struct vm_operations_struct armada_gem_vm_ops;
+
+static struct drm_driver armada_drm_driver = {
+	.load			= armada_drm_load,
+	.open			= NULL,
+	.preclose		= NULL,
+	.postclose		= NULL,
+	.lastclose		= NULL,
+	.unload			= armada_drm_unload,
+	.get_vblank_counter	= drm_vblank_count,
+	.enable_vblank		= armada_drm_enable_vblank,
+	.disable_vblank		= armada_drm_disable_vblank,
+	.irq_handler		= armada_drm_irq_handler,
+	.irq_postinstall	= armada_drm_irq_postinstall,
+	.irq_uninstall		= armada_drm_irq_uninstall,
+#ifdef CONFIG_DEBUG_FS
+	.debugfs_init		= armada_drm_debugfs_init,
+	.debugfs_cleanup	= armada_drm_debugfs_cleanup,
+#endif
+	.gem_init_object	= NULL,
+	.gem_free_object	= armada_gem_free_object,
+	.prime_handle_to_fd	= NULL,
+	.prime_fd_to_handle	= NULL,
+	.gem_prime_export	= NULL,
+	.gem_prime_import	= NULL,
+	.dumb_create		= armada_gem_dumb_create,
+	.dumb_map_offset	= armada_gem_dumb_map_offset,
+	.dumb_destroy		= armada_gem_dumb_destroy,
+	.gem_vm_ops		= &armada_gem_vm_ops,
+	.major			= 1,
+	.minor			= 0,
+	.name			= "armada-drm",
+	.desc			= "Armada SoC DRM",
+	.date			= "20120730",
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_HAVE_IRQ,
+	.ioctls			= armada_ioctls,
+	.fops			= &armada_drm_fops,
+};
+
+static int armada_drm_probe(struct platform_device *pdev)
+{
+	return drm_platform_init(&armada_drm_driver, pdev);
+}
+
+static int armada_drm_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&armada_drm_driver, pdev);
+	return 0;
+}
+
+static struct platform_driver armada_drm_platform_driver = {
+	.probe	= armada_drm_probe,
+	.remove	= armada_drm_remove,
+	.driver	= {
+		.owner	= THIS_MODULE,
+		.name	= "armada-drm",
+	},
+};
+
+static int __init armada_drm_init(void)
+{
+	armada_drm_driver.num_ioctls = DRM_ARRAY_SIZE(armada_ioctls);
+	return platform_driver_register(&armada_drm_platform_driver);
+}
+module_init(armada_drm_init);
+
+static void __exit armada_drm_exit(void)
+{
+	platform_driver_unregister(&armada_drm_platform_driver);
+}
+module_exit(armada_drm_exit);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Armada DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:armada-drm");
diff --git a/drivers/gpu/drm/armada/armada_fb.c b/drivers/gpu/drm/armada/armada_fb.c
new file mode 100644
index 0000000..8f72f13
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static void armada_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+
+	drm_framebuffer_cleanup(&dfb->fb);
+	if (dfb->obj)
+		drm_gem_object_unreference_unlocked(&dfb->obj->obj);
+	kfree(dfb);
+}
+
+static int armada_fb_create_handle(struct drm_framebuffer *fb,
+	struct drm_file *dfile, unsigned int *handle)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+	return drm_gem_handle_create(dfile, &dfb->obj->obj, handle);
+}
+
+static const struct drm_framebuffer_funcs armada_fb_funcs = {
+	.destroy	= armada_fb_destroy,
+	.create_handle	= armada_fb_create_handle,
+};
+
+/*
+ * Supported pixel formats:
+ *		T	R	G	B	BPP
+ * RGB565 		[15:11] [10:5]	[4:0]	16	DRM_FORMAT_RGB565
+ * BGR565		[4:0]	[10:5]	[15:11]	16	DRM_FORMAT_BGR565
+ * RGB1555	15	[14:10]	[9:5]	[4:0]	16	DRM_FORMAT_ARGB1555
+ * BGR1555	15	[4:0]	[9:5]	[14:10]	16	DRM_FORMAT_ABGR1555
+ * RGB888PACK		[23:16]	[15:8]	[7:0]	24	DRM_FORMAT_RGB888
+ * BGR888PACK		[7:0]	[15:8]	[23:16]	24	DRM_FORMAT_BGR888
+ * RGB888UNPACK		[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_XRGB8888
+ * BGR888UNPACK		[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_XBGR8888
+ * RGBA888	[31:24]	[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_ARGB8888
+ * BGRA888	[31:24]	[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_ABGR8888
+ *
+ * We don't currently support (note that the YUV pixel fields
+ * are incorrect, coming from the dovefb driver):
+ *			Y	U	V	BPP
+ * YUV422PACK		[15:8]	[7:4]	[3:0]	16
+ * YVU422PACK		[7:0]	[11:8]	[15:12]	16
+ * YUV422PLANAR		[15:8]	[7:4]	[3:0]	16
+ * YVU422PLANAR		[7:0]	[11:8]	[15:12]	16
+ * YUV420PLANAR		[11:4]	[3:2]	[1:0]	12
+ * YVU420PLANAR		[7:0]	[9:8]	[11:10]	12
+ * PSEUDOCOLOR					8
+ * UYVY422PACK		[11:4]	[15:12]	[3:0]	16
+ */
+int armada_framebuffer_create(struct drm_device *dev,
+	struct armada_framebuffer **dfbp, struct drm_mode_fb_cmd2 *mode,
+	struct armada_gem_object *obj)
+{
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	switch (mode->pixel_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
+	if (!dfb) {
+		DRM_ERROR("failed to allocate Armada fb object\n");
+		return -ENOMEM;
+	}
+
+	ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs);
+	if (ret) {
+		kfree(dfb);
+		return ret;
+	}
+
+	drm_helper_mode_fill_fb_struct(&dfb->fb, mode);
+
+	/*
+	 * Take a reference on our object - the caller is expected
+	 * to drop their reference to it.
+	 */
+	drm_gem_object_reference(&obj->obj);
+	dfb->obj = obj;
+	*dfbp = dfb;
+
+	return 0;
+}
+
+static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
+	struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode)
+{
+	struct armada_gem_object *obj;
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n",
+		mode->width, mode->height, mode->pixel_format,
+		mode->flags, mode->pitches[0], mode->pitches[1],
+		mode->pitches[2]);
+
+	/* We can only handle a single plane at the moment */
+	if (drm_format_num_planes(mode->pixel_format) > 1)
+		return ERR_PTR(-EINVAL);
+
+	obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		return ERR_PTR(-ENOENT);
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, mode, obj);
+	drm_gem_object_unreference_unlocked(&obj->obj);
+
+	if (ret) {
+		DRM_ERROR("failed to initialize framebuffer\n");
+		return ERR_PTR(ret);
+	}
+
+	return &dfb->fb;
+}
+
+static void armada_output_poll_changed(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh)
+		drm_fb_helper_hotplug_event(fbh);
+}
+
+const struct drm_mode_config_funcs armada_drm_mode_config_funcs = {
+	.fb_create		= armada_fb_create,
+	.output_poll_changed	= armada_output_poll_changed,
+};
diff --git a/drivers/gpu/drm/armada/armada_fb.h b/drivers/gpu/drm/armada/armada_fb.h
new file mode 100644
index 0000000..0c78d2b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_FB_H
+#define ARMADA_FB_H
+
+struct armada_framebuffer {
+	struct drm_framebuffer	fb;
+	struct armada_gem_object	*obj;
+};
+#define drm_fb_to_armada_fb(dfb) container_of(dfb, struct armada_framebuffer, fb)
+#define drm_fb_obj(fb) drm_fb_to_armada_fb(fb)->obj
+
+int armada_framebuffer_create(struct drm_device *, struct armada_framebuffer **,
+	struct drm_mode_fb_cmd2 *, struct armada_gem_object *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_fbdev.c b/drivers/gpu/drm/armada/armada_fbdev.c
new file mode 100644
index 0000000..001d2ec
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fbdev.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Written from the i915 driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static /*const*/ struct fb_ops armada_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_check_var	= drm_fb_helper_check_var,
+	.fb_set_par	= drm_fb_helper_set_par,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_pan_display	= drm_fb_helper_pan_display,
+	.fb_blank	= drm_fb_helper_blank,
+	.fb_setcmap	= drm_fb_helper_setcmap,
+	.fb_debug_enter	= drm_fb_helper_debug_enter,
+	.fb_debug_leave	= drm_fb_helper_debug_leave,
+};
+
+static int armada_fb_create(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	struct drm_device *dev = fbh->dev;
+	struct drm_mode_fb_cmd2 mode;
+	struct armada_framebuffer *dfb;
+	struct armada_gem_object *obj;
+	struct fb_info *info;
+	int size, ret;
+
+	memset(&mode, 0, sizeof(mode));
+	mode.width = sizes->surface_width;
+	mode.height = sizes->surface_height;
+	mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp);
+	mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+					sizes->surface_depth);
+
+	size = ALIGN(mode.pitches[0] * mode.height, PAGE_SIZE);
+	obj = armada_gem_alloc_private_object(dev, size);
+	if (!obj) {
+		DRM_ERROR("failed to allocate fb memory\n");
+		return -ENOMEM;
+	}
+
+	ret = armada_gem_linear_back(dev, obj);
+	if (ret) {
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return ret;
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, &mode, obj);
+	if (ret)
+		goto err_fbcreate;
+
+	mutex_lock(&dev->struct_mutex);
+
+	info = framebuffer_alloc(0, dev->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto err_fballoc;
+	}
+
+	ret = fb_alloc_cmap(&info->cmap, 256, 0);
+	if (ret) {
+		ret = -ENOMEM;
+		goto err_fbcmap;
+	}
+
+	strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id));
+	info->par = fbh;
+	info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+	info->fbops = &armada_fb_ops;
+	info->fix.smem_start = obj->phys_addr;
+	info->fix.smem_len = size;
+	info->screen_size = size;
+	info->screen_base = ioremap_wc(info->fix.smem_start, size);
+	if (!info->screen_base) {
+		ret = -ENOMEM;
+		goto err_ioremap;
+	}
+
+	fbh->fb = &dfb->fb;
+	fbh->fbdev = info;
+	drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth);
+	drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height);
+
+	DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08x\n",
+		dfb->fb.width, dfb->fb.height,
+		dfb->fb.bits_per_pixel, obj->phys_addr);
+
+	drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+
+	return 0;
+
+ err_ioremap:
+	fb_dealloc_cmap(&info->cmap);
+ err_fbcmap:
+	framebuffer_release(info);
+ err_fballoc:
+	mutex_unlock(&dev->struct_mutex);
+	dfb->fb.funcs->destroy(&dfb->fb);
+ err_fbcreate:
+	drm_gem_object_unreference_unlocked(&obj->obj);
+	return ret;
+}
+
+static void armada_fb_destroy(struct drm_fb_helper *fbh)
+{
+	struct fb_info *info = fbh->fbdev;
+
+	if (info) {
+		unregister_framebuffer(info);
+		iounmap(info->screen_base);
+		if (info->cmap.len)
+			fb_dealloc_cmap(&info->cmap);
+		framebuffer_release(info);
+	}
+
+	if (fbh->fb)
+		fbh->fb->funcs->destroy(fbh->fb);
+
+	drm_fb_helper_fini(fbh);
+}
+
+static int armada_fb_probe(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	int ret = 0;
+
+	if (!fbh->fb) {
+		ret = armada_fb_create(fbh, sizes);
+		if (ret == 0)
+			ret = 1;
+	}
+	return ret;
+}
+
+static struct drm_fb_helper_funcs armada_fb_helper_funcs = {
+	.gamma_set	= armada_drm_crtc_gamma_set,
+	.gamma_get	= armada_drm_crtc_gamma_get,
+	.fb_probe	= armada_fb_probe,
+};
+
+int armada_fbdev_init(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh;
+	int ret;
+
+	fbh = kzalloc(sizeof(*fbh), GFP_KERNEL);
+	if (!fbh)
+		return -ENOMEM;
+
+	priv->fbdev = fbh;
+
+	fbh->funcs = &armada_fb_helper_funcs;
+
+	ret = drm_fb_helper_init(dev, fbh, 1, 1);
+	if (ret) {
+		DRM_ERROR("failed to initialize drm fb helper\n");
+		goto err_fb_helper;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(fbh);
+	if (ret) {
+		DRM_ERROR("failed to add fb connectors\n");
+		goto err_fb_setup;
+	}
+
+	ret = drm_fb_helper_initial_config(fbh, 32);
+	if (ret) {
+		DRM_ERROR("failed to set initial config\n");
+		goto err_fb_setup;
+	}
+
+	return 0;
+ err_fb_setup:
+	drm_fb_helper_fini(fbh);
+ err_fb_helper:
+	kfree(fbh);
+	priv->fbdev = NULL;
+	return ret;
+}
+
+void armada_fbdev_fini(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh) {
+		armada_fb_destroy(fbh);
+		kfree(fbh);
+		priv->fbdev = NULL;
+	}
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
new file mode 100644
index 0000000..1d9c9cb
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.c
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/shmem_fs.h>
+#include <drm/drmP.h>
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+static int armada_gem_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct armada_gem_object *obj = drm_to_armada_gem(vma->vm_private_data);
+	unsigned long addr = (unsigned long)vmf->virtual_address;
+	unsigned long pfn = obj->phys_addr >> PAGE_SHIFT;
+	int ret;
+
+	pfn += (addr - vma->vm_start) >> PAGE_SHIFT;
+	ret = vm_insert_pfn(vma, addr, pfn);
+
+	switch (ret) {
+	case -EIO:
+	case -EAGAIN:
+		set_need_resched();
+	case 0:
+	case -ERESTARTSYS:
+	case -EINTR:
+		return VM_FAULT_NOPAGE;
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	default:
+		return VM_FAULT_SIGBUS;
+	}
+}
+
+const struct vm_operations_struct armada_gem_vm_ops = {
+	.fault	= armada_gem_vm_fault,
+	.open	= drm_gem_vm_open,
+	.close	= drm_gem_vm_close,
+};
+
+static size_t roundup_gem_size(size_t size)
+{
+	return roundup(size, PAGE_SIZE);
+}
+
+void armada_gem_free_object(struct drm_gem_object *obj)
+{
+	struct armada_gem_object *dobj = drm_to_armada_gem(obj);
+
+	DRM_DEBUG_DRIVER("release obj %p\n", dobj);
+
+	if (dobj->linear)
+		drm_mm_put_block(dobj->linear);
+
+	if (dobj->obj.map_list.map)
+		drm_gem_free_mmap_offset(&dobj->obj);
+
+	if (dobj->page) {
+		unsigned int order = get_order(dobj->obj.size);
+		__free_pages(dobj->page, order);
+	}
+
+	drm_gem_object_release(&dobj->obj);
+
+	kfree(dobj);
+}
+
+int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
+{
+	struct armada_private *priv = dev->dev_private;
+	size_t size = obj->obj.size;
+
+	if (obj->page || obj->linear)
+		return 0;
+
+	/* If it is a small allocation, try to get it from the system */
+	if (size < 1048576) {
+		unsigned int order = get_order(size);
+		struct page *p = alloc_pages(GFP_KERNEL, order);
+
+		if (p) {
+			unsigned sz = (size + 31) & ~31;
+			uintptr_t ptr;
+
+			obj->phys_addr = page_to_phys(p);
+			obj->dev_addr = obj->phys_addr;
+
+			/* FIXME: Hack around dirty cache */
+			ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
+			memset((void *)ptr, 0, PAGE_ALIGN(size));
+			while (sz) {
+				asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
+				ptr += 32;
+				sz -= 32;
+			}
+			dsb();
+
+			obj->page = p;
+		}
+	}
+
+	/* Otherwise, grab it from our linear allocation */
+	if (!obj->page) {
+		struct drm_mm_node *free;
+		unsigned align = min_t(unsigned, size, SZ_2M);
+		void __iomem *ptr;
+
+		free = drm_mm_search_free(&priv->linear, size, align, 0);
+		if (free)
+			obj->linear = drm_mm_get_block(free, size, align);
+		if (!obj->linear)
+			return -ENOSPC;
+
+		/* Ensure that the memory we're returning is cleared. */
+		ptr = ioremap_wc(obj->linear->start, size);
+		if (!ptr) {
+			drm_mm_put_block(obj->linear);
+			obj->linear = NULL;
+			return -ENOMEM;
+		}
+
+		memset_io(ptr, 0, size);
+		iounmap(ptr);
+
+		obj->phys_addr = obj->linear->start;
+		obj->dev_addr = obj->linear->start;
+	}
+
+	DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n",
+			 obj, obj->phys_addr, obj->dev_addr);
+
+	return 0;
+}
+
+struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *dev,
+	size_t size)
+{
+	struct armada_gem_object *obj;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_private_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	DRM_DEBUG_DRIVER("alloc private obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+struct armada_gem_object *armada_gem_alloc_object(struct drm_device *dev,
+	size_t size)
+{
+	struct armada_gem_object *obj;
+	struct address_space *mapping;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	mapping = obj->obj.filp->f_path.dentry->d_inode->i_mapping;
+	mapping_set_gfp_mask(mapping, GFP_HIGHUSER | __GFP_RECLAIMABLE);
+
+	DRM_DEBUG_DRIVER("alloc obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+/* Dumb alloc support */
+int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
+	struct drm_mode_create_dumb *args)
+{
+	struct armada_gem_object *dobj;
+	u32 handle;
+	size_t size;
+	int ret;
+
+	args->pitch = armada_pitch(args->width, args->bpp);
+	args->size = size = args->pitch * args->height;
+
+	dobj = armada_gem_alloc_private_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = armada_gem_linear_back(dev, dobj);
+	if (ret)
+		goto err;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle, uint64_t *offset)
+{
+	struct armada_gem_object *obj;
+	int ret = 0;
+
+	mutex_lock(&dev->struct_mutex);
+	obj = armada_gem_object_lookup(dev, file, handle);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		ret = -EINVAL;
+		goto err_unlock;
+	}
+
+	if (!obj->obj.map_list.map)
+		ret = drm_gem_create_mmap_offset(&obj->obj);
+
+	if (ret == 0) {
+		*offset = (u64)obj->obj.map_list.hash.key << PAGE_SHIFT;
+		DRM_DEBUG_DRIVER("handle %#x offset %llx\n", handle, *offset);
+	}
+
+	drm_gem_object_unreference(&obj->obj);
+ err_unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle)
+{
+	return drm_gem_handle_delete(file, handle);
+}
+
+/* Private driver gem ioctls */
+int armada_gem_create_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create *args = data;
+	struct armada_gem_object *dobj;
+	size_t size;
+	u32 handle;
+	int ret;
+
+	if (args->size == 0) {
+		args->pitch = armada_pitch(args->width, args->bpp);
+		args->size = size = args->pitch * args->height;
+	} else {
+		size = args->size;
+	}
+
+	dobj = armada_gem_alloc_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_create_phys_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create_phys *arg = data;
+	struct armada_gem_object *dobj;
+	u32 handle;
+	int ret;
+
+	dobj = armada_gem_alloc_private_object(dev, arg->size);
+	if (dobj) {
+		dobj->phys_addr = arg->phys;
+		dobj->dev_addr = arg->phys;
+	}
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret) {
+		drm_gem_object_release(&dobj->obj);
+		return ret;
+	}
+
+	/* drop reference from allocate - handle holds it now */
+	drm_gem_object_unreference(&dobj->obj);
+
+	arg->handle = handle;
+
+	return 0;
+}
+
+/* Map a shmem-backed object into process memory space */
+int armada_gem_mmap_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_mmap *args = data;
+	struct armada_gem_object *dobj;
+	unsigned long addr;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	if (!dobj->obj.filp) {
+		drm_gem_object_unreference(&dobj->obj);
+		return -EINVAL;
+	}
+
+	addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE,
+	               MAP_SHARED, args->offset);
+	drm_gem_object_unreference(&dobj->obj);
+	if (IS_ERR_VALUE(addr))
+		return addr;
+
+	args->addr = addr;
+
+	return 0;
+}
+
+int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_prop *arg = data;
+	struct armada_gem_object *dobj;
+	int ret;
+
+	ret = mutex_lock_interruptible(&dev->struct_mutex);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, arg->handle);
+	if (dobj == NULL) {
+		ret = -ENOENT;
+		goto unlock;
+	}
+
+	arg->phys = dobj->phys_addr;
+	ret = 0;
+	drm_gem_object_unreference(&dobj->obj);
+
+ unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_pwrite *args = data;
+	struct armada_gem_object *dobj;
+	char __user *ptr;
+	int ret;
+
+	DRM_DEBUG_DRIVER("handle %u off %u size %u ptr 0x%llx\n",
+		args->handle, args->offset, args->size, args->ptr);
+
+	if (args->size == 0)
+		return 0;
+
+	ptr = (char __user *)(uintptr_t)args->ptr;
+
+	if (!access_ok(VERIFY_READ, ptr, args->size))
+		return -EFAULT;
+
+	ret = fault_in_multipages_readable(ptr, args->size);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	/* Don't allow pwrite to drm linear backed objects */
+	if (dobj->linear)
+		return -EINVAL;
+
+	if (args->offset > dobj->obj.size ||
+	    args->size > dobj->obj.size - args->offset) {
+		DRM_ERROR("invalid size: object size %u\n", dobj->obj.size);
+		ret = -EINVAL;
+		goto unref;
+	}
+
+	{
+		void *data = phys_to_virt(dobj->phys_addr) + args->offset;
+		ret = copy_from_user(data, ptr, args->size) ? -EFAULT : 0;
+
+		if (ret == 0 && dobj->update)
+			dobj->update(dobj->update_data);
+	}
+
+  unref:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.h b/drivers/gpu/drm/armada/armada_gem.h
new file mode 100644
index 0000000..f500546
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_GEM_H
+#define ARMADA_GEM_H
+
+/* GEM */
+struct armada_gem_object {
+	struct drm_gem_object	obj;
+	struct drm_mm_node	*linear;
+	phys_addr_t		phys_addr;
+	resource_size_t		dev_addr;
+	struct page		*page;
+	void			(*update)(void *);
+	void			*update_data;
+};
+
+#define drm_to_armada_gem(o) container_of(o, struct armada_gem_object, obj)
+
+void armada_gem_free_object(struct drm_gem_object *);
+int armada_gem_linear_back(struct drm_device *, struct armada_gem_object *);
+struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *, size_t);
+int armada_gem_dumb_create(struct drm_file *, struct drm_device *,
+	struct drm_mode_create_dumb *);
+int armada_gem_dumb_map_offset(struct drm_file *, struct drm_device *,
+	uint32_t, uint64_t *);
+int armada_gem_dumb_destroy(struct drm_file *, struct drm_device *,
+	uint32_t);
+
+static inline struct armada_gem_object *armada_gem_object_lookup(
+	struct drm_device *dev, struct drm_file *dfile, unsigned handle)
+{
+	struct drm_gem_object *obj = drm_gem_object_lookup(dev, dfile, handle);
+
+	return obj ? drm_to_armada_gem(obj) : NULL;
+}
+#endif
diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h
new file mode 100644
index 0000000..f9e539b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_hw.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_HW_H
+#define ARMADA_HW_H
+
+/*
+ * Note: the following registers are written from IRQ context:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *  LCD_SPU_DMA_START_ADDR_[YUV][01], LCD_SPU_DMA_PITCH_YC,
+ *  LCD_SPU_DMA_PITCH_UV, LCD_SPU_DMA_OVSA_HPXL_VLN,
+ *  LCD_SPU_DMA_HPXL_VLN, LCD_SPU_DZM_HPXL_VLN, LCD_SPU_DMA_CTRL0
+ */
+enum {
+	LCD_SPU_ADV_REG			= 0x0084,
+	LCD_SPU_DMA_START_ADDR_Y0	= 0x00c0,
+	LCD_SPU_DMA_START_ADDR_U0	= 0x00c4,
+	LCD_SPU_DMA_START_ADDR_V0	= 0x00c8,
+	LCD_CFG_DMA_START_ADDR_0	= 0x00cc,
+	LCD_SPU_DMA_START_ADDR_Y1	= 0x00d0,
+	LCD_SPU_DMA_START_ADDR_U1	= 0x00d4,
+	LCD_SPU_DMA_START_ADDR_V1	= 0x00d8,
+	LCD_CFG_DMA_START_ADDR_1	= 0x00dc,
+	LCD_SPU_DMA_PITCH_YC		= 0x00e0,
+	LCD_SPU_DMA_PITCH_UV		= 0x00e4,
+	LCD_SPU_DMA_OVSA_HPXL_VLN	= 0x00e8,
+	LCD_SPU_DMA_HPXL_VLN		= 0x00ec,
+	LCD_SPU_DZM_HPXL_VLN		= 0x00f0,
+	LCD_CFG_GRA_START_ADDR0		= 0x00f4,
+	LCD_CFG_GRA_START_ADDR1		= 0x00f8,
+	LCD_CFG_GRA_PITCH		= 0x00fc,
+	LCD_SPU_GRA_OVSA_HPXL_VLN	= 0x0100,
+	LCD_SPU_GRA_HPXL_VLN		= 0x0104,
+	LCD_SPU_GZM_HPXL_VLN		= 0x0108,
+	LCD_SPU_HWC_OVSA_HPXL_VLN	= 0x010c,
+	LCD_SPU_HWC_HPXL_VLN		= 0x0110,
+	LCD_SPUT_V_H_TOTAL		= 0x0114,
+	LCD_SPU_V_H_ACTIVE		= 0x0118,
+	LCD_SPU_H_PORCH			= 0x011c,
+	LCD_SPU_V_PORCH			= 0x0120,
+	LCD_SPU_BLANKCOLOR		= 0x0124,
+	LCD_SPU_ALPHA_COLOR1		= 0x0128,
+	LCD_SPU_ALPHA_COLOR2		= 0x012c,
+	LCD_SPU_COLORKEY_Y		= 0x0130,
+	LCD_SPU_COLORKEY_U		= 0x0134,
+	LCD_SPU_COLORKEY_V		= 0x0138,
+	LCD_CFG_RDREG4F			= 0x013c,
+	LCD_SPU_SPI_RXDATA		= 0x0140,
+	LCD_SPU_ISA_RSDATA		= 0x0144,
+	LCD_SPU_HWC_RDDAT		= 0x0158,
+	LCD_SPU_GAMMA_RDDAT		= 0x015c,
+	LCD_SPU_PALETTE_RDDAT		= 0x0160,
+	LCD_SPU_IOPAD_IN		= 0x0178,
+	LCD_CFG_RDREG5F			= 0x017c,
+	LCD_SPU_SPI_CTRL		= 0x0180,
+	LCD_SPU_SPI_TXDATA		= 0x0184,
+	LCD_SPU_SMPN_CTRL		= 0x0188,
+	LCD_SPU_DMA_CTRL0		= 0x0190,
+	LCD_SPU_DMA_CTRL1		= 0x0194,
+	LCD_SPU_SRAM_CTRL		= 0x0198,
+	LCD_SPU_SRAM_WRDAT		= 0x019c,
+	LCD_SPU_SRAM_PARA0		= 0x01a0,
+	LCD_SPU_SRAM_PARA1		= 0x01a4,
+	LCD_CFG_SCLK_DIV		= 0x01a8,
+	LCD_SPU_CONTRAST		= 0x01ac,
+	LCD_SPU_SATURATION		= 0x01b0,
+	LCD_SPU_CBSH_HUE		= 0x01b4,
+	LCD_SPU_DUMB_CTRL		= 0x01b8,
+	LCD_SPU_IOPAD_CONTROL		= 0x01bc,
+	LCD_SPU_IRQ_ENA			= 0x01c0,
+	LCD_SPU_IRQ_ISR			= 0x01c4,
+};
+
+/* For LCD_SPU_ADV_REG */
+enum {
+	ADV_VSYNC_L_OFF	= 0xfff << 20,
+	ADV_GRACOLORKEY	= 1 << 19,
+	ADV_VIDCOLORKEY	= 1 << 18,
+	ADV_HWC32BLEND	= 1 << 15,
+	ADV_HWC32ARGB	= 1 << 14,
+	ADV_HWC32ENABLE	= 1 << 13,
+	ADV_VSYNCOFFEN	= 1 << 12,
+	ADV_VSYNC_H_OFF	= 0xfff << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL0 */
+enum {
+	CFG_NOBLENDING	= 1 << 31,
+	CFG_GAMMA_ENA	= 1 << 30,
+	CFG_CBSH_ENA	= 1 << 29,
+	CFG_PALETTE_ENA	= 1 << 28,
+	CFG_ARBFAST_ENA	= 1 << 27,
+	CFG_HWC_1BITMOD	= 1 << 26,
+	CFG_HWC_1BITENA	= 1 << 25,
+	CFG_HWC_ENA	= 1 << 24,
+	CFG_DMAFORMAT	= 0xf << 20,
+	CFG_DMA_565	= 0x0 << 20,
+	CFG_DMA_1555	= 0x1 << 20,
+	CFG_DMA_888PACK	= 0x2 << 20,
+	CFG_DMA_X888	= 0x3 << 20,
+	CFG_DMA_8888	= 0x4 << 20,
+	CFG_DMA_422PACK	= 0x5 << 20,
+	CFG_DMA_422	= 0x6 << 20,
+	CFG_DMA_420	= 0x7 << 20,
+	CFG_DMA_PSEUDO4	= 0x9 << 20,
+	CFG_DMA_PSEUDO8	= 0xa << 20,
+	CFG_GRAFORMAT	= 0xf << 16,
+	CFG_GRA_565	= 0x0 << 16,
+	CFG_GRA_1555	= 0x1 << 16,
+	CFG_GRA_888PACK	= 0x2 << 16,
+	CFG_GRA_X888	= 0x3 << 16,
+	CFG_GRA_8888	= 0x4 << 16,
+	CFG_GRA_422PACK	= 0x5 << 16,
+	CFG_GRA_422	= 0x6 << 16,
+	CFG_GRA_420	= 0x7 << 16,
+	CFG_GRA_PSEUDO4	= 0x9 << 16,
+	CFG_GRA_PSEUDO8	= 0xa << 16,
+	CFG_GRA_FTOGGLE	= 1 << 15,
+	CFG_GRA_HSMOOTH	= 1 << 14,
+	CFG_GRA_TSTMODE	= 1 << 13,
+	CFG_GRA_SWAPRB	= 1 << 12,
+	CFG_GRA_SWAPUV	= 1 << 11,
+	CFG_GRA_SWAPYU	= 1 << 10,
+	CFG_YUV2RGB_GRA	= 1 << 9,
+	CFG_GRA_ENA	= 1 << 8,
+	CFG_DMA_FTOGGLE	= 1 << 7,
+	CFG_DMA_HSMOOTH	= 1 << 6,
+	CFG_DMA_TSTMODE	= 1 << 5,
+	CFG_DMA_SWAPRB	= 1 << 4,
+	CFG_DMA_SWAPUV	= 1 << 3,
+	CFG_DMA_SWAPYU	= 1 << 2,
+	CFG_YUV2RGB_DMA	= 1 << 1,
+	CFG_DMA_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL1 */
+enum {
+	CFG_FRAME_TRIG	= 1 << 31,
+	CFG_VSYNC_INV	= 1 << 27,
+	CFG_CKMODE_MASK	= 0x7 << 24,
+	CFG_CKMODE_DIS	= 0x0 << 24,
+	CFG_CKMODE_Y	= 0x1 << 24,
+	CFG_CKMODE_U	= 0x2 << 24,
+	CFG_CKMODE_RGB	= 0x3 << 24,
+	CFG_CKMODE_V	= 0x4 << 24,
+	CFG_CKMODE_R	= 0x5 << 24,
+	CFG_CKMODE_G	= 0x6 << 24,
+	CFG_CKMODE_B	= 0x7 << 24,
+	CFG_CARRY	= 1 << 23,
+	CFG_GATED_CLK	= 1 << 21,
+	CFG_PWRDN_ENA	= 1 << 20,
+	CFG_DSCALE_MASK	= 0x3 << 18,
+	CFG_DSCALE_NONE	= 0x0 << 18,
+	CFG_DSCALE_HALF	= 0x1 << 18,
+	CFG_DSCALE_QUAR = 0x2 << 18,
+	CFG_ALPHAM_MASK	= 0x3 << 16,
+	CFG_ALPHAM_VIDEO= 0x0 << 16,
+	CFG_ALPHAM_GRA	= 0x1 << 16,
+	CFG_ALPHAM_CFG	= 0x2 << 16,
+	CFG_ALPHA_MASK	= 0xff << 8,
+	CFG_PIXCMD_MASK	= 0xff,
+};
+
+/* For LCD_SPU_SRAM_CTRL */
+enum {
+	SRAM_READ	= 0 << 14,
+	SRAM_WRITE	= 2 << 14,
+	SRAM_INIT	= 3 << 14,
+	SRAM_HWC32_RAMR	= 0xc << 8,
+	SRAM_HWC32_RAMG	= 0xd << 8,
+	SRAM_HWC32_RAMB	= 0xe << 8,
+	SRAM_HWC32_TRAN	= 0xf << 8,
+	SRAM_HWC	= 0xf << 8,
+};
+
+/* For LCD_SPU_SRAM_PARA1 */
+enum {
+	CFG_CSB_256x32	= 1 << 15,
+	CFG_CSB_256x24	= 1 << 14,
+	CFG_CSB_256x8	= 1 << 13,
+	CFG_PDWN256x32	= 1 << 7,
+	CFG_PDWN256x24	= 1 << 6,
+	CFG_PDWN256x8	= 1 << 5,
+	CFG_PDWN32x32	= 1 << 3,
+	CFG_PDWN16x66	= 1 << 2,
+	CFG_PDWN32x66	= 1 << 1,
+	CFG_PDWN64x66	= 1 << 0,
+};
+
+/* For LCD_SPU_DUMB_CTRL */
+enum {
+	DUMB16_RGB565_0	= 0x0 << 28,
+	DUMB16_RGB565_1	= 0x1 << 28,
+	DUMB18_RGB666_0	= 0x2 << 28,
+	DUMB18_RGB666_1	= 0x3 << 28,
+	DUMB12_RGB444_0	= 0x4 << 28,
+	DUMB12_RGB444_1	= 0x5 << 28,
+	DUMB24_RGB888_0	= 0x6 << 28,
+	DUMB_BLANK	= 0x7 << 28,
+	DUMB_MASK	= 0xf << 28,
+	CFG_BIAS_OUT	= 1 << 8,
+	CFG_REV_RGB	= 1 << 7,
+	CFG_INV_CBLANK	= 1 << 6,
+	CFG_INV_CSYNC	= 1 << 5,
+	CFG_INV_HENA	= 1 << 4,	/* Normally active high */
+	CFG_INV_VSYNC	= 1 << 3,	/* Normally active high */
+	CFG_INV_HSYNC	= 1 << 2,	/* Normally active high */
+	CFG_INV_PCLK	= 1 << 1,
+	CFG_DUMB_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_IOPAD_CONTROL */
+enum {
+	CFG_VSCALE_LN_EN	= 3 << 18,
+	CFG_GRA_VM_ENA		= 1 << 15,
+	CFG_DMA_VM_ENA		= 1 << 13,
+	CFG_CMD_VM_ENA		= 1 << 11,
+	CFG_CSC_MASK		= 3 << 8,
+	CFG_CSC_CCIR709		= 1 << 9,
+	CFG_CSC_PROF		= 1 << 8,
+	CFG_IOPAD_MASK		= 0xf << 0,
+	CFG_IOPAD_DUMB24	= 0x0 << 0,
+	CFG_IOPAD_DUMB18SPI	= 0x1 << 0,
+	CFG_IOPAD_DUMB18GPIO	= 0x2 << 0,
+	CFG_IOPAD_DUMB16SPI	= 0x3 << 0,
+	CFG_IOPAD_DUMB16GPIO	= 0x4 << 0,
+	CFG_IOPAD_DUMB12GPIO	= 0x5 << 0,
+	CFG_IOPAD_SMART18	= 0x6 << 0,
+	CFG_IOPAD_SMART16	= 0x7 << 0,
+	CFG_IOPAD_SMART8	= 0x8 << 0,
+};
+
+#define IOPAD_DUMB24                0x0
+
+/* For LCD_SPU_IRQ_ENA */
+enum {
+	DMA_FRAME_IRQ0_ENA	= 1 << 31,
+	DMA_FRAME_IRQ1_ENA	= 1 << 30,
+	DMA_FRAME_IRQ_ENA	= DMA_FRAME_IRQ0_ENA | DMA_FRAME_IRQ1_ENA,
+	DMA_FF_UNDERFLOW_ENA	= 1 << 29,
+	GRA_FRAME_IRQ0_ENA	= 1 << 27,
+	GRA_FRAME_IRQ1_ENA	= 1 << 26,
+	GRA_FRAME_IRQ_ENA	= GRA_FRAME_IRQ0_ENA | GRA_FRAME_IRQ1_ENA,
+	GRA_FF_UNDERFLOW_ENA	= 1 << 25,
+	VSYNC_IRQ_ENA		= 1 << 23,
+	DUMB_FRAMEDONE_ENA	= 1 << 22,
+	TWC_FRAMEDONE_ENA	= 1 << 21,
+	HWC_FRAMEDONE_ENA	= 1 << 20,
+	SLV_IRQ_ENA		= 1 << 19,
+	SPI_IRQ_ENA		= 1 << 18,
+	PWRDN_IRQ_ENA		= 1 << 17,
+	ERR_IRQ_ENA		= 1 << 16,
+	CLEAN_SPU_IRQ_ISR	= 0xffff,
+};
+
+/* For LCD_SPU_IRQ_ISR */
+enum {
+	DMA_FRAME_IRQ0		= 1 << 31,
+	DMA_FRAME_IRQ1		= 1 << 30,
+	DMA_FRAME_IRQ		= DMA_FRAME_IRQ0 | DMA_FRAME_IRQ1,
+	DMA_FF_UNDERFLOW	= 1 << 29,
+	GRA_FRAME_IRQ0		= 1 << 27,
+	GRA_FRAME_IRQ1		= 1 << 26,
+	GRA_FRAME_IRQ		= GRA_FRAME_IRQ0 | GRA_FRAME_IRQ1,
+	GRA_FF_UNDERFLOW	= 1 << 25,
+	VSYNC_IRQ		= 1 << 23,
+	DUMB_FRAMEDONE		= 1 << 22,
+	TWC_FRAMEDONE		= 1 << 21,
+	HWC_FRAMEDONE		= 1 << 20,
+	SLV_IRQ			= 1 << 19,
+	SPI_IRQ			= 1 << 18,
+	PWRDN_IRQ		= 1 << 17,
+	ERR_IRQ			= 1 << 16,
+	DMA_FRAME_IRQ0_LEVEL	= 1 << 15,
+	DMA_FRAME_IRQ1_LEVEL	= 1 << 14,
+	DMA_FRAME_CNT_ISR	= 3 << 12,
+	GRA_FRAME_IRQ0_LEVEL	= 1 << 11,
+	GRA_FRAME_IRQ1_LEVEL	= 1 << 10,
+	GRA_FRAME_CNT_ISR	= 3 << 8,
+	VSYNC_IRQ_LEVEL		= 1 << 7,
+	DUMB_FRAMEDONE_LEVEL	= 1 << 6,
+	TWC_FRAMEDONE_LEVEL	= 1 << 5,
+	HWC_FRAMEDONE_LEVEL	= 1 << 4,
+	SLV_FF_EMPTY		= 1 << 3,
+	DMA_FF_ALLEMPTY		= 1 << 2,
+	GRA_FF_ALLEMPTY		= 1 << 1,
+	PWRDN_IRQ_LEVEL		= 1 << 0,
+};
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctl.h b/drivers/gpu/drm/armada/armada_ioctl.h
new file mode 100644
index 0000000..619ec2c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctl.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  With inspiration from the i915 driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_ARMADA_IOCTL_H
+#define DRM_ARMADA_IOCTL_H
+
+#define DRM_ARMADA_GEM_CREATE		0x00
+#define DRM_ARMADA_GEM_CREATE_PHYS	0x01
+#define DRM_ARMADA_GEM_MMAP		0x02
+#define DRM_ARMADA_GEM_PWRITE		0x03
+#define DRM_ARMADA_GEM_PROP		0x04
+#define DRM_ARMADA_OVERLAY_PUT_IMAGE	0x06
+#define DRM_ARMADA_OVERLAY_ATTRS		0x07
+
+#define ARMADA_IOCTL(dir,name,str) \
+	DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
+
+struct drm_armada_gem_create {
+	uint32_t height;
+	uint32_t width;
+	uint32_t bpp;
+	uint32_t handle;
+	uint32_t pitch;
+	uint32_t size;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE \
+	ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
+
+struct drm_armada_gem_create_phys {
+	uint32_t size;
+	uint32_t handle;
+	uint64_t phys;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
+	ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
+
+struct drm_armada_gem_mmap {
+	uint32_t handle;
+	uint32_t pad;
+	uint64_t offset;
+	uint64_t size;
+	uint64_t addr;
+};
+#define DRM_IOCTL_ARMADA_GEM_MMAP \
+	ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
+
+struct drm_armada_gem_pwrite {
+	uint32_t handle;
+	uint32_t offset;
+	uint32_t size;
+	uint64_t ptr;
+};
+#define DRM_IOCTL_ARMADA_GEM_PWRITE \
+	ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
+
+struct drm_armada_gem_prop {
+	uint64_t phys;
+	uint32_t handle;
+};
+#define DRM_IOCTL_ARMADA_GEM_PROP \
+	ARMADA_IOCTL(IOWR, GEM_PROP, gem_prop)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_put_image {
+	uint32_t flags;
+#define ARMADA_OVERLAY_TYPE_MASK          0x000000ff
+#define ARMADA_OVERLAY_YUV_PLANAR         0x00000001
+#define ARMADA_OVERLAY_YUV_PACKED         0x00000002
+#define ARMADA_OVERLAY_RGB                0x00000003
+#define ARMADA_OVERLAY_DEPTH_MASK		0x0000ff00
+#define ARMADA_OVERLAY_RGB24		0x00001000
+#define ARMADA_OVERLAY_RGB16		0x00002000
+#define ARMADA_OVERLAY_RGB15		0x00003000
+#define ARMADA_OVERLAY_YUV422		0x00000100
+#define ARMADA_OVERLAY_YUV411		0x00000200
+#define ARMADA_OVERLAY_YUV420		0x00000300
+#define ARMADA_OVERLAY_YUV410		0x00000400
+#define ARMADA_OVERLAY_SWAP_MASK		0x00ff0000
+#define ARMADA_OVERLAY_NO_SWAP		0x00000000
+#define ARMADA_OVERLAY_UV_SWAP		0x00010000
+#define ARMADA_OVERLAY_Y_SWAP		0x00020000
+#define ARMADA_OVERLAY_Y_AND_UV_SWAP	0x00030000
+#define ARMADA_OVERLAY_FLAGS_MASK		0xff000000
+#define ARMADA_OVERLAY_ENABLE		0x01000000
+	uint32_t bo_handle;
+	uint16_t stride_Y;
+	uint16_t stride_UV;
+	uint32_t offset_Y;
+	uint32_t offset_U;
+	uint32_t offset_V;
+	uint16_t src_width;
+	uint16_t src_height;
+	uint16_t src_scan_width;
+	uint16_t src_scan_height;
+	uint32_t crtc_id;
+	uint16_t dst_x;
+	uint16_t dst_y;
+	uint16_t dst_width;
+	uint16_t dst_height;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_PUT_IMAGE \
+	ARMADA_IOCTL(IOW, OVERLAY_PUT_IMAGE, overlay_put_image)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_attrs {
+	uint32_t flags;
+#define ARMADA_OVERLAY_UPDATE_ATTRS	(1<<0)
+#define ARMADA_OVERLAY_UPDATE_GAMMA	(1<<1)
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+	uint32_t gamma0;
+	uint32_t gamma1;
+	uint32_t gamma2;
+	uint32_t gamma3;
+	uint32_t gamma4;
+	uint32_t gamma5;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_ATTRS \
+	ARMADA_IOCTL(IOWR, OVERLAY_ATTRS, overlay_attrs)
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctlP.h b/drivers/gpu/drm/armada/armada_ioctlP.h
new file mode 100644
index 0000000..8e6089e
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctlP.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_IOCTLP_H
+#define ARMADA_IOCTLP_H
+
+#define ARMADA_IOCTL_PROTO(name)\
+extern int armada_##name##_ioctl(struct drm_device *, void *, struct drm_file *)
+
+ARMADA_IOCTL_PROTO(gem_create);
+ARMADA_IOCTL_PROTO(gem_create_phys);
+ARMADA_IOCTL_PROTO(gem_mmap);
+ARMADA_IOCTL_PROTO(gem_prop);
+ARMADA_IOCTL_PROTO(gem_pwrite);
+ARMADA_IOCTL_PROTO(overlay_put_image);
+ARMADA_IOCTL_PROTO(overlay_attrs);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_output.c b/drivers/gpu/drm/armada/armada_output.c
new file mode 100644
index 0000000..3b95f0c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "drm_helper.h"
+#include "armada_output.h"
+#include "armada_drm.h"
+
+struct armada_connector {
+	struct drm_connector conn;
+	const struct armada_output_type *type;
+};
+
+#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = conn->encoder;
+
+	return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
+}
+
+static enum drm_connector_status armada_drm_connector_detect(
+	struct drm_connector *conn, bool force)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+	enum drm_connector_status status = connector_status_disconnected;
+
+	if (dconn->type->detect) {
+		status = dconn->type->detect(conn, force);
+	} else {
+		struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+
+		if (enc)
+			status = encoder_helper_funcs(enc)->detect(enc, conn);
+	}
+
+	return status;
+}
+
+static void armada_drm_connector_destroy(struct drm_connector *conn)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	drm_sysfs_connector_remove(conn);
+	drm_connector_cleanup(conn);
+	kfree(dconn);
+}
+
+static int armada_drm_connector_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	if (!dconn->type->set_property)
+		return -EINVAL;
+
+	return dconn->type->set_property(conn, property, value);
+}
+
+static const struct drm_connector_funcs armada_drm_conn_funcs = {
+	.dpms		= drm_helper_connector_dpms,
+	.fill_modes	= drm_helper_probe_single_connector_modes,
+	.detect		= armada_drm_connector_detect,
+	.destroy	= armada_drm_connector_destroy,
+	.set_property	= armada_drm_connector_set_property,
+};
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+void armada_drm_encoder_commit(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
+{
+	return true;
+}
+
+/* Shouldn't this be a generic helper function? */
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int valid = MODE_BAD;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		valid = slave->slave_funcs->mode_valid(encoder, mode);
+	}
+	return valid;
+}
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int rc = -EINVAL;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		rc = slave->slave_funcs->set_property(encoder, conn, property,
+						      value);
+	}
+	return rc;
+}
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data)
+{
+	struct armada_connector *dconn;
+	int ret;
+
+	dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
+	if (!dconn)
+		return -ENOMEM;
+
+	dconn->type = type;
+
+	ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
+				 type->connector_type);
+	if (ret) {
+		DRM_ERROR("unable to init connector\n");
+		goto err_destroy_dconn;
+	}
+
+	ret = type->create(&dconn->conn, data);
+	if (ret)
+		goto err_conn;
+
+	ret = drm_sysfs_connector_add(&dconn->conn);
+	if (ret)
+		goto err_sysfs;
+
+	return 0;
+
+ err_sysfs:
+	if (dconn->conn.encoder)
+		dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
+ err_conn:
+	drm_connector_cleanup(&dconn->conn);
+ err_destroy_dconn:
+	kfree(dconn);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_output.h b/drivers/gpu/drm/armada/armada_output.h
new file mode 100644
index 0000000..d655655
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CONNETOR_H
+#define ARMADA_CONNETOR_H
+
+#define encoder_helper_funcs(encoder) \
+	((struct drm_encoder_helper_funcs *)encoder->helper_private)
+
+struct armada_output_type {
+	int connector_type;
+	enum drm_connector_status (*detect)(struct drm_connector *, bool);
+	int (*create)(struct drm_connector *, const void *);
+	int (*set_property)(struct drm_connector *, struct drm_property *, uint64_t);
+};
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn);
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder);
+void armada_drm_encoder_commit(struct drm_encoder *encoder);
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj);
+
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode);
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value);
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c
new file mode 100644
index 0000000..68c4efd
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_overlay.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include "drm_helper.h"
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+struct armada_overlay {
+	struct armada_gem_object *obj;
+	struct armada_gem_object *old_obj;
+	struct armada_crtc *dcrtc;
+	uint32_t crtc_id;
+	uint32_t cached_flags;
+	uint32_t cached_cfg;
+
+	uint32_t stride_yc;
+	uint32_t stride_uv;
+	uint32_t dst_yx;
+	uint32_t src_hw;
+	uint32_t dst_hw;
+	uint32_t cfg;
+
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+
+	struct armada_vbl_event vbl_update;
+
+	struct armada_regs update[13];
+
+	wait_queue_head_t vbl_wait;
+};
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+static void armada_ovl_update(struct armada_crtc *dcrtc, void *data)
+{
+	struct armada_overlay *ovl = container_of(data, struct armada_overlay, update);
+	armada_drm_crtc_update_regs(dcrtc, data);
+	wake_up(&ovl->vbl_wait);
+}
+
+static void armada_ovl_update_attr(struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+	uint32_t key = ovl->color_key;
+        uint32_t r, g, b;
+
+        r = (key & 0x0000ff);
+        g = (key & 0x00ff00) >> 8;
+        b = (key & 0xff0000) >> 16;
+
+	writel_relaxed(r << 8 | r << 16 | r << 24, dcrtc->base + LCD_SPU_COLORKEY_Y);
+	writel_relaxed(g << 8 | g << 16 | g << 24, dcrtc->base + LCD_SPU_COLORKEY_U);
+	writel_relaxed(b << 8 | b << 16 | b << 24, dcrtc->base + LCD_SPU_COLORKEY_V);
+
+	writel_relaxed(0x00004000, dcrtc->base + LCD_SPU_CONTRAST);
+	writel_relaxed(0x40000000, dcrtc->base + LCD_SPU_SATURATION);
+	writel_relaxed(0x00002000, dcrtc->base + LCD_SPU_CBSH_HUE);
+
+	spin_lock_irq(&dcrtc->irq_lock);
+	armada_updatel(CFG_CKMODE_RGB | CFG_ALPHAM_GRA,
+		     CFG_CKMODE_MASK | CFG_ALPHAM_MASK | CFG_ALPHA_MASK,
+		     dcrtc->base + LCD_SPU_DMA_CTRL1);
+
+	armada_updatel(ADV_GRACOLORKEY, 0, dcrtc->base + LCD_SPU_ADV_REG);
+	spin_unlock_irq(&dcrtc->irq_lock);
+}
+
+static int armada_check_planar(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp;
+
+	DRM_DEBUG_DRIVER("stride Y%#x UV%#x offset Y%#x U%#x V%#x obj %#x\n",
+		args->stride_Y, args->stride_UV,
+		args->offset_Y, args->offset_U, args->offset_V,
+		obj->obj.size);
+
+	if (args->src_scan_width     > args->stride_Y ||
+	    args->src_scan_width / 2 > args->stride_UV)
+		return -EINVAL;
+
+	tmp = args->stride_Y * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	tmp = args->stride_UV * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_U > obj->obj.size - tmp ||
+	    args->offset_V > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int armada_check_packed(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp = args->stride_Y * args->src_height;
+
+	if (args->src_scan_width * 2 > args->stride_Y ||
+	    tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct armada_crtc *armada_crtc_lookup(struct drm_device *dev, uint32_t id)
+{
+	struct drm_crtc *crtc = drm_crtc_find(dev, id);
+	return crtc ? drm_to_armada_crtc(crtc) : NULL;
+}
+
+static void armada_ovl_release_old(struct armada_overlay *ovl)
+{
+	if (ovl->old_obj) {
+		drm_gem_object_unreference(&ovl->old_obj->obj);
+		ovl->old_obj = NULL;
+	}
+}
+
+/*
+ * This should be called with both dev->struct_mutex and
+ * the mode_config mutexes held.
+ */
+void armada_drm_overlay_off(struct drm_device *dev, struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+
+	ovl->cfg = 0;
+
+	if (dcrtc) {
+		/* Disable overlay */
+		armada_drm_vbl_event_remove_unlocked(dcrtc, &ovl->vbl_update);
+
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_updatel(0, CFG_DMA_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
+
+		ovl->dcrtc->overlay = NULL;
+		ovl->dcrtc = NULL;
+		ovl->crtc_id = ~0;
+	}
+
+	armada_ovl_release_old(ovl);
+
+	if (ovl->obj) {
+		drm_gem_object_unreference(&ovl->obj->obj);
+		ovl->obj = NULL;
+	}
+}
+
+static int armada_ovl_check_dst(const struct drm_armada_overlay_put_image *args,
+	const struct drm_display_mode *mode)
+{
+	if (args->dst_x < mode->hdisplay &&
+	    args->dst_width <= mode->hdisplay - args->dst_x &&
+	    args->dst_y < mode->vdisplay &&
+	    args->dst_height <= mode->vdisplay - args->dst_y)
+		return 0;
+	return -EINVAL;
+}
+
+static int armada_ovl_compute_cfg(struct armada_overlay *ovl, uint32_t flags)
+{
+	uint32_t cfg = CFG_DMA_HSMOOTH | CFG_CBSH_ENA;
+
+	switch (flags & ARMADA_OVERLAY_TYPE_MASK) {
+	case ARMADA_OVERLAY_YUV_PLANAR:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:	/* Planar YUV422 */
+			cfg |= CFG_DMA_422;
+			break;
+		case ARMADA_OVERLAY_YUV420:	/* Planar YUV420 */
+			cfg |= CFG_DMA_420;
+			break;
+		default:
+			DRM_ERROR("bad planar depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("planar and requested swap\n");
+			return -EINVAL;
+		}
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_YUV_PACKED:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:
+			cfg |= CFG_DMA_422PACK;
+			break;
+		default:
+			DRM_ERROR("bad packed depth\n");
+			return -EINVAL;
+		}
+		if (flags & (ARMADA_OVERLAY_SWAP_MASK & ~ARMADA_OVERLAY_Y_AND_UV_SWAP)) {
+			DRM_ERROR("bad overlay swap\n");
+			return -EINVAL;
+		}
+		/*
+		 *                            [0:7] [8:15] [16:23] [24:31]
+		 * 0:			YUY2:   Y     U       Y       V
+		 * UV_SWAP:		YVYU:   Y     V       Y       U
+		 * Y_SWAP:		UYVY:   U     Y       V       Y
+		 * Y_SWAP | UV_SWAP:	VYUY:   V     Y       U       Y
+		 *
+		 * Default ordering in memory:  U     Y0      V       Y1
+		 *
+		 * Image fourcc 59565955 UYVY flags 01020102 -> correct
+		 * Image fourcc 32595559 YUY2 flags 01000102 -> wrong U/V swapped
+		 */
+		if (flags & ARMADA_OVERLAY_UV_SWAP)
+			cfg |= CFG_DMA_SWAPUV;
+		if (!(flags & ARMADA_OVERLAY_Y_SWAP))
+			cfg ^= CFG_DMA_SWAPYU | CFG_DMA_SWAPUV;
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_RGB:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_RGB24:	/* 3 byte RGB */
+			cfg |= CFG_DMA_888PACK;
+			break;
+		case ARMADA_OVERLAY_RGB16:        /* 2 byte RGB */
+			cfg |= CFG_DMA_565;
+			break;
+		case ARMADA_OVERLAY_RGB15:        /* 2 byte RGB */
+			cfg |= CFG_DMA_1555;
+			break;
+		default:
+			DRM_ERROR("bad RGB depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("RGB and requested swap\n");
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		DRM_ERROR("bad overlay type\n");
+		return -EINVAL;
+	}
+
+	cfg |= CFG_DMA_ENA;
+
+	ovl->cached_flags = flags;
+	ovl->cached_cfg = cfg;
+	return 0;
+}
+
+int armada_overlay_put_image_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_put_image *args = data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+	struct armada_gem_object *obj;
+	struct armada_crtc *dcrtc;
+	uint32_t stride_uv, stride_yc, src_hw, dst_hw, dst_yx;
+	bool planar = false;
+	int ret, idx;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (!(args->flags & ARMADA_OVERLAY_ENABLE)) {
+		mutex_lock(&dev->mode_config.mutex);
+		mutex_lock(&dev->struct_mutex);
+
+		armada_drm_overlay_off(dev, ovl);
+
+		mutex_unlock(&dev->struct_mutex);
+		mutex_unlock(&dev->mode_config.mutex);
+
+		return 0;
+	}
+
+//	DRM_DEBUG_DRIVER("flags %x handle %x src %dx%d dst %dx%d+%d+%d\n",
+//		args->flags, args->bo_handle, args->src_scan_width, args->src_scan_height,
+//		args->dst_width, args->dst_height, args->dst_x, args->dst_y);
+
+	if (!ovl->dcrtc || ovl->crtc_id != args->crtc_id) {
+		dcrtc = armada_crtc_lookup(dev, args->crtc_id);
+		if (!dcrtc)
+			return -ENOENT;
+	} else {
+		dcrtc = ovl->dcrtc;
+	}
+
+	if (args->flags != ovl->cached_flags) {
+		ret = armada_ovl_compute_cfg(ovl, args->flags);
+		if (ret)
+			return ret;
+	}
+
+	obj = armada_gem_object_lookup(dev, file, args->bo_handle);
+	if (!obj)
+		return -ENOENT;
+
+	ret = wait_event_timeout(ovl->vbl_wait, list_empty(&ovl->vbl_update.node), HZ/25);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&dev->mode_config.mutex);
+	mutex_lock(&dev->struct_mutex);
+
+	/* Prevent updates */
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	if (ovl->dcrtc != dcrtc) {
+		armada_drm_overlay_off(dev, ovl);
+
+		ovl->crtc_id = args->crtc_id;
+		ovl->dcrtc = dcrtc;
+		dcrtc->overlay = ovl;
+
+		armada_ovl_update_attr(ovl);
+	}
+
+	ret = armada_ovl_check_dst(args, &ovl->dcrtc->crtc.mode);
+	if (ret)
+		goto err_unref;
+
+	planar = (args->flags & ARMADA_OVERLAY_TYPE_MASK) == ARMADA_OVERLAY_YUV_PLANAR;
+	if (planar)
+		ret = armada_check_planar(args, obj);
+	else
+		ret = armada_check_packed(args, obj);
+	if (ret)
+		goto err_unref;
+
+	if (ovl->dcrtc->interlaced) {
+		args->dst_y /= 2;
+		args->dst_height /= 2;
+	}
+
+	idx = 0;
+	if (ovl->obj != obj) {
+		uint32_t start_y, start_u, start_v;
+
+		/* switch to new object */
+		armada_ovl_release_old(ovl);
+		ovl->old_obj = ovl->obj;
+		ovl->obj = obj;
+
+		start_y = obj->dev_addr + args->offset_Y;
+		if (planar) {
+			start_u = obj->dev_addr + args->offset_U;
+			start_v = obj->dev_addr + args->offset_V;
+		} else {
+			start_u = start_y;
+			start_v = start_y;
+		}
+
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y0);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U0);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V0);
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y1);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U1);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V1);
+	} else {
+		drm_gem_object_unreference(&obj->obj);
+	}
+
+	stride_yc = args->stride_Y << 16 | args->stride_Y;
+	stride_uv = args->stride_UV << 16 | args->stride_UV;
+
+	if (ovl->stride_yc != stride_yc || ovl->stride_uv != stride_uv) {
+		ovl->stride_yc = stride_yc;
+		ovl->stride_uv = stride_uv;
+		armada_reg_queue_set(ovl->update, idx, stride_yc, LCD_SPU_DMA_PITCH_YC);
+		armada_reg_queue_set(ovl->update, idx, stride_uv, LCD_SPU_DMA_PITCH_UV);
+	}
+
+	src_hw = args->src_scan_height << 16 | args->src_scan_width;
+	dst_hw = args->dst_height << 16 | args->dst_width;
+	if (ovl->src_hw != src_hw || ovl->dst_hw != dst_hw) {
+		ovl->src_hw = src_hw;
+		ovl->dst_hw = dst_hw;
+
+		armada_reg_queue_set(ovl->update, idx, dst_hw, LCD_SPU_DZM_HPXL_VLN);
+		armada_reg_queue_set(ovl->update, idx, src_hw, LCD_SPU_DMA_HPXL_VLN);
+	}
+
+	dst_yx = args->dst_y << 16 | args->dst_x;
+	if (ovl->dst_yx != dst_yx) {
+		ovl->dst_yx = dst_yx;
+		armada_reg_queue_set(ovl->update, idx, dst_yx, LCD_SPU_DMA_OVSA_HPXL_VLN);
+	}
+
+	/* Update overlay DMA settings */
+	if (ovl->cfg != ovl->cached_cfg) {
+		ovl->cfg = ovl->cached_cfg;
+		armada_reg_queue_mod(ovl->update, idx, ovl->cached_cfg,
+			CFG_DMAFORMAT | CFG_DMA_FTOGGLE | CFG_DMA_HSMOOTH |
+			CFG_DMA_TSTMODE | CFG_DMA_SWAPRB | CFG_DMA_SWAPUV |
+			CFG_DMA_SWAPYU | CFG_YUV2RGB_DMA | CFG_DMA_ENA,
+			LCD_SPU_DMA_CTRL0);
+	}
+	if (idx) {
+		armada_reg_queue_end(ovl->update, idx);
+		armada_drm_vbl_event_add(dcrtc, &ovl->vbl_update);
+	}
+
+ err_unref:
+	if (ret)
+		drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+	mutex_unlock(&dev->mode_config.mutex);
+
+	return ret;
+}
+
+int armada_overlay_attrs_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_attrs *args = data;
+        struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_ATTRS) {
+		if (args->brightness < -128 || args->brightness > 127 ||
+		    args->contrast > 255 || args->saturation > 1023)
+			return -EINVAL;
+
+		ovl->color_key = args->color_key;
+		ovl->brightness = args->brightness;
+		ovl->contrast = args->contrast;
+		ovl->saturation = args->saturation;
+
+		mutex_lock(&dev->mode_config.mutex);
+		if (ovl->dcrtc)
+			armada_ovl_update_attr(ovl);
+		mutex_unlock(&dev->mode_config.mutex);
+	} else {
+		args->color_key = ovl->color_key;
+		args->brightness = ovl->brightness;
+		args->contrast = ovl->contrast;
+		args->saturation = ovl->saturation;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_GAMMA) {
+		/* args->gamma0..5 */
+	}
+
+	return 0;
+}
+
+int armada_overlay_create(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl;
+
+	ovl = kzalloc(sizeof(*ovl), GFP_KERNEL);
+	if (!ovl)
+		return -ENOMEM;
+
+	priv->overlay = ovl;
+
+	init_waitqueue_head(&ovl->vbl_wait);
+	armada_drm_vbl_event_init(&ovl->vbl_update, armada_ovl_update, ovl->update);
+
+	ovl->color_key = 0x0101fe;
+	ovl->brightness = -19;
+	ovl->contrast = 75;
+	ovl->saturation = 146;
+
+	return 0;
+}
+
+void armada_overlay_destroy(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	kfree(ovl);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.c b/drivers/gpu/drm/armada/armada_slave.c
new file mode 100644
index 0000000..422a345
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "armada_drm.h"
+#include "armada_output.h"
+#include "armada_slave.h"
+
+static int armada_drm_slave_get_modes(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+	int count = 0;
+
+	if (enc) {
+		struct drm_encoder_slave *slave = to_encoder_slave(enc);
+
+		count = slave->slave_funcs->get_modes(enc, conn);
+	}
+
+	return count;
+}
+
+static void armada_drm_slave_destroy(struct drm_encoder *enc)
+{
+	struct drm_encoder_slave *slave = to_encoder_slave(enc);
+	struct i2c_client *client = drm_i2c_encoder_get_client(enc);
+
+	if (slave->slave_funcs)
+		slave->slave_funcs->destroy(enc);
+	if (client)
+		i2c_put_adapter(client->adapter);
+
+	drm_encoder_cleanup(&slave->base);
+	kfree(slave);
+}
+
+static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
+	.destroy	= armada_drm_slave_destroy,
+};
+
+static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
+	.get_modes	= armada_drm_slave_get_modes,
+	.mode_valid	= armada_drm_slave_encoder_mode_valid,
+	.best_encoder	= armada_drm_connector_encoder,
+};
+
+static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
+	.dpms = drm_i2c_encoder_dpms,
+	.save = drm_i2c_encoder_save,
+	.restore = drm_i2c_encoder_restore,
+	.mode_fixup = drm_i2c_encoder_mode_fixup,
+	.prepare = drm_i2c_encoder_prepare,
+	.commit = drm_i2c_encoder_commit,
+	.mode_set = drm_i2c_encoder_mode_set,
+	.detect = drm_i2c_encoder_detect,
+};
+
+static int armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
+{
+	const struct armada_drm_slave_config *config = data;
+	struct drm_encoder_slave *slave;
+	struct i2c_adapter *adap;
+	int ret;
+
+	conn->interlace_allowed = config->interlace_allowed;
+	conn->doublescan_allowed = config->doublescan_allowed;
+	conn->polled = config->polled;
+
+	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
+
+	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+	if (!slave)
+		return -ENOMEM;
+
+	slave->base.possible_crtcs = config->crtcs;
+
+	adap = i2c_get_adapter(config->i2c_adapter_id);
+	if (!adap) {
+		kfree(slave);
+		return -EPROBE_DEFER;
+	}
+
+	ret = drm_encoder_init(conn->dev, &slave->base,
+			       &armada_drm_slave_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret) {
+		DRM_ERROR("unable to init encoder\n");
+		i2c_put_adapter(adap);
+		kfree(slave);
+		return ret;
+	}
+
+	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
+	i2c_put_adapter(adap);
+	if (ret) {
+		DRM_ERROR("unable to init encoder slave\n");
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
+
+	ret = slave->slave_funcs->create_resources(&slave->base, conn);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	conn->encoder = &slave->base;
+
+	return ret;
+}
+
+static const struct armada_output_type armada_drm_conn_slave = {
+	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
+	.create		= armada_drm_conn_slave_create,
+	.set_property	= armada_drm_slave_encoder_set_property,
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *config)
+{
+	return armada_output_create(dev, &armada_drm_conn_slave, config);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
new file mode 100644
index 0000000..1b86696
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_TDA19988_H
+#define ARMADA_TDA19988_H
+
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+
+struct armada_drm_slave_config {
+	int i2c_adapter_id;
+	uint32_t crtcs;
+	uint8_t polled;
+	bool interlace_allowed;
+	bool doublescan_allowed;
+	struct i2c_board_info info;
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/drm_helper.h b/drivers/gpu/drm/armada/drm_helper.h
new file mode 100644
index 0000000..d9f2e8d
--- /dev/null
+++ b/drivers/gpu/drm/armada/drm_helper.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_HELPER_H
+#define DRM_HELPER_H
+
+/* Useful helpers I wish the DRM core would provide */
+
+#include <drm/drmP.h>
+
+static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
+	return mo ? obj_to_crtc(mo) : NULL;
+}
+
+static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
+	return mo ? obj_to_encoder(mo) : NULL;
+}
+
+#endif
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 5/8] drm/i2c: nxp-tda998x: fix npix/nline programming
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:32   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:32 UTC (permalink / raw)
  To: linux-arm-kernel

The npix/nline registers are supposed to be programmed with the total
number of pixels/lines, not the displayed pixels/lines, and not minus
one either.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index dc0428d..cd1526c 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -587,8 +587,8 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,
 		reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
 
 	reg_write(encoder, REG_VIDFORMAT, 0x00);
-	reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
-	reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
+	reg_write16(encoder, REG_NPIX_MSB, mode->htotal);
+	reg_write16(encoder, REG_NLINE_MSB, mode->vtotal);
 	reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
 	reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
 	reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 5/8] drm/i2c: nxp-tda998x: fix npix/nline programming
@ 2013-06-09 19:32   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:32 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

The npix/nline registers are supposed to be programmed with the total
number of pixels/lines, not the displayed pixels/lines, and not minus
one either.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index dc0428d..cd1526c 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -587,8 +587,8 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,
 		reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
 
 	reg_write(encoder, REG_VIDFORMAT, 0x00);
-	reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
-	reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
+	reg_write16(encoder, REG_NPIX_MSB, mode->htotal);
+	reg_write16(encoder, REG_NLINE_MSB, mode->vtotal);
 	reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
 	reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
 	reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 6/8] drm/i2c: nxp-tda998x: prepare for video input configuration
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:34   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:34 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |   16 ++++++++++------
 1 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index cd1526c..c0d17ae 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -32,6 +32,9 @@ struct tda998x_priv {
 	uint16_t rev;
 	uint8_t current_page;
 	int dpms;
+	u8 vip_cntrl_0;
+	u8 vip_cntrl_1;
+	u8 vip_cntrl_2;
 };
 
 #define to_tda998x_priv(x)  ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
@@ -448,12 +451,9 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 		reg_write(encoder, REG_ENA_VP_1, 0xff);
 		reg_write(encoder, REG_ENA_VP_2, 0xff);
 		/* set muxing after enabling ports: */
-		reg_write(encoder, REG_VIP_CNTRL_0,
-				VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
-		reg_write(encoder, REG_VIP_CNTRL_1,
-				VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
-		reg_write(encoder, REG_VIP_CNTRL_2,
-				VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
+		reg_write(encoder, REG_VIP_CNTRL_0, priv->vip_cntrl_0);
+		reg_write(encoder, REG_VIP_CNTRL_1, priv->vip_cntrl_1);
+		reg_write(encoder, REG_VIP_CNTRL_2, priv->vip_cntrl_2);
 		break;
 	case DRM_MODE_DPMS_OFF:
 		/* disable audio and video ports */
@@ -823,6 +823,10 @@ tda998x_encoder_init(struct i2c_client *client,
 	if (!priv)
 		return -ENOMEM;
 
+	priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3);
+	priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1);
+	priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5);
+
 	priv->current_page = 0;
 	priv->cec = i2c_new_dummy(client->adapter, 0x34);
 	priv->dpms = DRM_MODE_DPMS_OFF;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 6/8] drm/i2c: nxp-tda998x: prepare for video input configuration
@ 2013-06-09 19:34   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:34 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |   16 ++++++++++------
 1 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index cd1526c..c0d17ae 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -32,6 +32,9 @@ struct tda998x_priv {
 	uint16_t rev;
 	uint8_t current_page;
 	int dpms;
+	u8 vip_cntrl_0;
+	u8 vip_cntrl_1;
+	u8 vip_cntrl_2;
 };
 
 #define to_tda998x_priv(x)  ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
@@ -448,12 +451,9 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 		reg_write(encoder, REG_ENA_VP_1, 0xff);
 		reg_write(encoder, REG_ENA_VP_2, 0xff);
 		/* set muxing after enabling ports: */
-		reg_write(encoder, REG_VIP_CNTRL_0,
-				VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
-		reg_write(encoder, REG_VIP_CNTRL_1,
-				VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
-		reg_write(encoder, REG_VIP_CNTRL_2,
-				VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
+		reg_write(encoder, REG_VIP_CNTRL_0, priv->vip_cntrl_0);
+		reg_write(encoder, REG_VIP_CNTRL_1, priv->vip_cntrl_1);
+		reg_write(encoder, REG_VIP_CNTRL_2, priv->vip_cntrl_2);
 		break;
 	case DRM_MODE_DPMS_OFF:
 		/* disable audio and video ports */
@@ -823,6 +823,10 @@ tda998x_encoder_init(struct i2c_client *client,
 	if (!priv)
 		return -ENOMEM;
 
+	priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3);
+	priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1);
+	priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5);
+
 	priv->current_page = 0;
 	priv->cec = i2c_new_dummy(client->adapter, 0x34);
 	priv->dpms = DRM_MODE_DPMS_OFF;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 7/8] drm/i2c: nxp-tda998x: add video and audio input configuration
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:35   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:35 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |  308 ++++++++++++++++++++++++++++++++++++-
 include/drm/i2c/tda998x.h         |   30 ++++
 2 files changed, 331 insertions(+), 7 deletions(-)
 create mode 100644 include/drm/i2c/tda998x.h

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index c0d17ae..ad773a6 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -23,7 +23,7 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_encoder_slave.h>
 #include <drm/drm_edid.h>
-
+#include <drm/i2c/tda998x.h>
 
 #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
 
@@ -32,9 +32,11 @@ struct tda998x_priv {
 	uint16_t rev;
 	uint8_t current_page;
 	int dpms;
+	bool is_hdmi_sink;
 	u8 vip_cntrl_0;
 	u8 vip_cntrl_1;
 	u8 vip_cntrl_2;
+	struct tda998x_encoder_params params;
 };
 
 #define to_tda998x_priv(x)  ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
@@ -71,10 +73,13 @@ struct tda998x_priv {
 # define I2C_MASTER_DIS_MM        (1 << 0)
 # define I2C_MASTER_DIS_FILT      (1 << 1)
 # define I2C_MASTER_APP_STRT_LAT  (1 << 2)
+#define REG_FEAT_POWERDOWN        REG(0x00, 0x0e)     /* read/write */
+# define FEAT_POWERDOWN_SPDIF     (1 << 3)
 #define REG_INT_FLAGS_0           REG(0x00, 0x0f)     /* read/write */
 #define REG_INT_FLAGS_1           REG(0x00, 0x10)     /* read/write */
 #define REG_INT_FLAGS_2           REG(0x00, 0x11)     /* read/write */
 # define INT_FLAGS_2_EDID_BLK_RD  (1 << 1)
+#define REG_ENA_ACLK              REG(0x00, 0x16)     /* read/write */
 #define REG_ENA_VP_0              REG(0x00, 0x18)     /* read/write */
 #define REG_ENA_VP_1              REG(0x00, 0x19)     /* read/write */
 #define REG_ENA_VP_2              REG(0x00, 0x1a)     /* read/write */
@@ -113,6 +118,7 @@ struct tda998x_priv {
 #define REG_VIP_CNTRL_5           REG(0x00, 0x25)     /* write */
 # define VIP_CNTRL_5_CKCASE       (1 << 0)
 # define VIP_CNTRL_5_SP_CNT(x)    (((x) & 3) << 1)
+#define REG_MUX_AP                REG(0x00, 0x26)     /* read/write */
 #define REG_MUX_VP_VIP_OUT        REG(0x00, 0x27)     /* read/write */
 #define REG_MAT_CONTRL            REG(0x00, 0x80)     /* write */
 # define MAT_CONTRL_MAT_SC(x)     (((x) & 3) << 0)
@@ -175,6 +181,12 @@ struct tda998x_priv {
 # define HVF_CNTRL_1_PAD(x)       (((x) & 3) << 4)
 # define HVF_CNTRL_1_SEMI_PLANAR  (1 << 6)
 #define REG_RPT_CNTRL             REG(0x00, 0xf0)     /* write */
+#define REG_I2S_FORMAT            REG(0x00, 0xfc)     /* read/write */
+# define I2S_FORMAT(x)            (((x) & 3) << 0)
+#define REG_AIP_CLKSEL            REG(0x00, 0xfd)     /* write */
+# define AIP_CLKSEL_FS(x)         (((x) & 3) << 0)
+# define AIP_CLKSEL_CLK_POL(x)    (((x) & 1) << 2)
+# define AIP_CLKSEL_AIP(x)        (((x) & 7) << 3)
 
 
 /* Page 02h: PLL settings */
@@ -216,6 +228,11 @@ struct tda998x_priv {
 
 
 /* Page 10h: information frames and packets */
+#define REG_IF1_HB0               REG(0x10, 0x20)     /* read/write */
+#define REG_IF2_HB0               REG(0x10, 0x40)     /* read/write */
+#define REG_IF3_HB0               REG(0x10, 0x60)     /* read/write */
+#define REG_IF4_HB0               REG(0x10, 0x80)     /* read/write */
+#define REG_IF5_HB0               REG(0x10, 0xa0)     /* read/write */
 
 
 /* Page 11h: audio settings and content info packets */
@@ -225,10 +242,33 @@ struct tda998x_priv {
 # define AIP_CNTRL_0_LAYOUT       (1 << 2)
 # define AIP_CNTRL_0_ACR_MAN      (1 << 5)
 # define AIP_CNTRL_0_RST_CTS      (1 << 6)
+#define REG_CA_I2S                REG(0x11, 0x01)     /* read/write */
+# define CA_I2S_CA_I2S(x)         (((x) & 31) << 0)
+# define CA_I2S_HBR_CHSTAT        (1 << 6)
+#define REG_LATENCY_RD            REG(0x11, 0x04)     /* read/write */
+#define REG_ACR_CTS_0             REG(0x11, 0x05)     /* read/write */
+#define REG_ACR_CTS_1             REG(0x11, 0x06)     /* read/write */
+#define REG_ACR_CTS_2             REG(0x11, 0x07)     /* read/write */
+#define REG_ACR_N_0               REG(0x11, 0x08)     /* read/write */
+#define REG_ACR_N_1               REG(0x11, 0x09)     /* read/write */
+#define REG_ACR_N_2               REG(0x11, 0x0a)     /* read/write */
+#define REG_CTS_N                 REG(0x11, 0x0c)     /* read/write */
+# define CTS_N_K(x)               (((x) & 7) << 0)
+# define CTS_N_M(x)               (((x) & 3) << 4)
 #define REG_ENC_CNTRL             REG(0x11, 0x0d)     /* read/write */
 # define ENC_CNTRL_RST_ENC        (1 << 0)
 # define ENC_CNTRL_RST_SEL        (1 << 1)
 # define ENC_CNTRL_CTL_CODE(x)    (((x) & 3) << 2)
+#define REG_DIP_FLAGS             REG(0x11, 0x0e)     /* read/write */
+# define DIP_FLAGS_ACR            (1 << 0)
+# define DIP_FLAGS_GC             (1 << 1)
+#define REG_DIP_IF_FLAGS          REG(0x11, 0x0f)     /* read/write */
+# define DIP_IF_FLAGS_IF1         (1 << 1)
+# define DIP_IF_FLAGS_IF2         (1 << 2)
+# define DIP_IF_FLAGS_IF3         (1 << 3)
+# define DIP_IF_FLAGS_IF4         (1 << 4)
+# define DIP_IF_FLAGS_IF5         (1 << 5)
+#define REG_CH_STAT_B(x)          REG(0x11, 0x14 + (x)) /* read/write */
 
 
 /* Page 12h: HDCP and OTP */
@@ -344,6 +384,23 @@ reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
 	return ret;
 }
 
+static void
+reg_write_range(struct drm_encoder *encoder, uint16_t reg, uint8_t *p, int cnt)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t buf[cnt+1];
+	int ret;
+
+	buf[0] = REG2ADDR(reg);
+	memcpy(&buf[1], p, cnt);
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, buf, cnt + 1);
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
 static uint8_t
 reg_read(struct drm_encoder *encoder, uint16_t reg)
 {
@@ -424,11 +481,192 @@ tda998x_reset(struct drm_encoder *encoder)
 	reg_write(encoder, REG_MUX_VP_VIP_OUT, 0x24);
 }
 
+static uint8_t tda998x_cksum(uint8_t *buf, size_t bytes)
+{
+	uint8_t sum = 0;
+
+	while (bytes--)
+		sum += *buf++;
+	return (255 - sum) + 1;
+}
+
+#define HB(x) (x)
+#define PB(x) (HB(2) + 1 + (x))
+
+static void
+tda998x_write_if(struct drm_encoder *encoder, uint8_t bit, uint16_t addr,
+		 uint8_t *buf, size_t size)
+{
+	buf[PB(0)] = tda998x_cksum(buf, size);
+
+	reg_clear(encoder, REG_DIP_IF_FLAGS, bit);
+	reg_write_range(encoder, addr, buf, size);
+	reg_set(encoder, REG_DIP_IF_FLAGS, bit);
+}
+
+static void
+tda998x_write_aif(struct drm_encoder *encoder, struct tda998x_encoder_params *p)
+{
+	uint8_t buf[PB(5) + 1];
+
+	buf[HB(0)] = 0x84;
+	buf[HB(1)] = 0x01;
+	buf[HB(2)] = 10;
+	buf[PB(0)] = 0;
+	buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */
+	buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */
+	buf[PB(4)] = p->audio_frame[4];
+	buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */
+
+	tda998x_write_if(encoder, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf,
+			 sizeof(buf));
+}
+
+static void
+tda998x_write_avi(struct drm_encoder *encoder, struct drm_display_mode *mode)
+{
+	uint8_t buf[PB(13) + 1];
+
+	memset(buf, 0, sizeof(buf));
+	buf[HB(0)] = 0x82;
+	buf[HB(1)] = 0x02;
+	buf[HB(2)] = 13;
+	buf[PB(4)] = drm_match_cea_mode(mode);
+
+	tda998x_write_if(encoder, DIP_IF_FLAGS_IF2, REG_IF2_HB0, buf,
+			 sizeof(buf));
+}
+
+static void tda998x_audio_mute(struct drm_encoder *encoder, bool on)
+{
+	if (on) {
+		reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO);
+		reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO);
+		reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+	} else {
+		reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+	}
+}
+
+static void
+tda998x_configure_audio(struct drm_encoder *encoder, struct tda998x_encoder_params *p)
+{
+	uint8_t buf[6], clksel_aip, clksel_fs, ca_i2s, cts_n;
+	uint32_t n;
+
+	/* SetAudioPortConfig */
+	reg_write(encoder, REG_ENA_AP, p->audio_cfg);
+	/* SetAudioClockPortConfig */
+	reg_write(encoder, REG_ENA_ACLK, p->audio_clk_cfg);
+
+	/*
+	 * layout = channelAllocation ? 1 : 0;
+	 * AudioInSetConfig(format, i2sFormat, channelAllocation,
+	 *   HDMITX_CHAN_NO_CHANGE, HDMITX_CLKPOLDSD_NO_CHANGE,
+	 *   HDMITX_SWAPDSD_NO_CHANGE, layout, HDMITX_LATENCY_CURRENT,
+	 *   dstRate)
+	 */
+	switch (p->audio_format) {
+	case AFMT_SPDIF:
+		reg_write(encoder, REG_MUX_AP, 0x40);
+		clksel_aip = AIP_CLKSEL_AIP(0);
+		/* FS64SPDIF */
+		clksel_fs = AIP_CLKSEL_FS(2);
+		cts_n = CTS_N_M(3) | CTS_N_K(3);
+		ca_i2s = 0;
+		break;
+
+	case AFMT_I2S:
+		reg_write(encoder, REG_MUX_AP, 0x64);
+		clksel_aip = AIP_CLKSEL_AIP(1);
+		/* ACLK */
+		clksel_fs = AIP_CLKSEL_FS(0);
+		cts_n = CTS_N_M(3) | CTS_N_K(3);
+		ca_i2s = CA_I2S_CA_I2S(0 /* channel allocation */);
+		break;
+	}
+
+	reg_write(encoder, REG_AIP_CLKSEL, clksel_aip);
+//	reg_write(encoder, REG_CA_I2S, ca_i2s);
+//	reg_write(encoder, REG_I2S_FORMAT, I2S_FORMAT(0 /* i2s format */));
+	reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT);
+	/* latency? */
+
+	/* get video format */
+
+	/*
+	 * ctsRef = HDMITX_CTSREF_FS64SPDIF, uCtsX = HDMITX_CTSX_64
+	 * AudioInSetCts(ctsRef, rate, VidFmt, vOutFreq,
+	 *   HDMITX_CTS_AUTO, uCtsX, HDMITX_CTSK_USE_CTSX,
+	 *   HDMITX_CTSMTS_USE_CTSX, dstRate)
+	 */
+	/* Auto CTS */
+	reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_ACR_MAN);
+	reg_write(encoder, REG_CTS_N, cts_n);
+	reg_write(encoder, REG_AUDIO_DIV, 3);
+
+	/*
+	 * This is the approximate value of N, which happens to be
+	 * the recommended values for non-coherent clocks.
+	 */
+	n = 128 * p->audio_sample_rate / 1000;
+
+	/* Write the CTS and N values */
+	buf[0] = 0x44;
+	buf[1] = 0x42;
+	buf[2] = 0x01;
+	buf[3] = n;
+	buf[4] = n >> 8;
+	buf[5] = n >> 16;
+	reg_write_range(encoder, REG_ACR_CTS_0, buf, 6);
+
+	/* Set CTS clock reference */
+	reg_write(encoder, REG_AIP_CLKSEL, clksel_aip | clksel_fs);
+
+	/* Reset CTS generator */
+	reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+	reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+
+	/* Write the channel status */
+	buf[0] = 0x04;
+	buf[1] = 0x00;
+	buf[2] = 0x00;
+	buf[3] = 0xf1;
+	reg_write_range(encoder, REG_CH_STAT_B(0), buf, 4);
+
+	tda998x_audio_mute(encoder, true);
+	mdelay(20);
+	tda998x_audio_mute(encoder, false);
+
+	/* Write the audio information packet */
+	tda998x_write_aif(encoder, p);
+}
+
 /* DRM encoder functions */
 
 static void
 tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
 {
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+	struct tda998x_encoder_params *p = params;
+
+	priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
+			    (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
+			    VIP_CNTRL_0_SWAP_B(p->swap_b) |
+			    (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0);
+	priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) |
+			    (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) |
+			    VIP_CNTRL_1_SWAP_D(p->swap_d) |
+			    (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0);
+	priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) |
+			    (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) |
+			    VIP_CNTRL_2_SWAP_F(p->swap_f) |
+			    (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
+
+	priv->params = *p;
+
+	if (p->audio_cfg)
+		tda998x_configure_audio(encoder, p);
 }
 
 static void
@@ -446,7 +684,8 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 	switch (mode) {
 	case DRM_MODE_DPMS_ON:
 		/* enable audio and video ports */
-		reg_write(encoder, REG_ENA_AP, 0xff);
+//		reg_write(encoder, REG_ENA_AP, priv->ena_ap);
+//		reg_write(encoder, REG_ENA_ACLK, priv->ena_aclk);
 		reg_write(encoder, REG_ENA_VP_0, 0xff);
 		reg_write(encoder, REG_ENA_VP_1, 0xff);
 		reg_write(encoder, REG_ENA_VP_2, 0xff);
@@ -457,7 +696,7 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 		break;
 	case DRM_MODE_DPMS_OFF:
 		/* disable audio and video ports */
-		reg_write(encoder, REG_ENA_AP, 0x00);
+//		reg_write(encoder, REG_ENA_AP, 0x00);
 		reg_write(encoder, REG_ENA_VP_0, 0x00);
 		reg_write(encoder, REG_ENA_VP_1, 0x00);
 		reg_write(encoder, REG_ENA_VP_2, 0x00);
@@ -608,17 +847,31 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,
 	reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
 	reg_write16(encoder, REG_REFLINE_MSB, ref_line);
 
-	reg = TBG_CNTRL_1_VHX_EXT_DE |
-			TBG_CNTRL_1_VHX_EXT_HS |
-			TBG_CNTRL_1_VHX_EXT_VS |
-			TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
+	reg = TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
 			TBG_CNTRL_1_VH_TGL_2;
+	/*
+	 * It is questionable whether this is correct - the nxp driver
+	 * does not set VH_TGL_2 and the below for all display modes.
+	 */
 	if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
 		reg |= TBG_CNTRL_1_VH_TGL_0;
 	reg_set(encoder, REG_TBG_CNTRL_1, reg);
 
 	/* must be last register set: */
 	reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
+
+	/* Only setup the info frames if the sink is HDMI */
+	if (priv->is_hdmi_sink) {
+		/* We need to turn HDMI HDCP stuff on to get audio through */
+		reg_clear(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+		reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1));
+		reg_set(encoder, REG_TX33, TX33_HDMI);
+
+		tda998x_write_avi(encoder, adjusted_mode);
+
+		if (priv->params.audio_cfg)
+			tda998x_configure_audio(encoder, &priv->params);
+	}
 }
 
 static enum drm_connector_status
@@ -744,12 +997,14 @@ static int
 tda998x_encoder_get_modes(struct drm_encoder *encoder,
 			 struct drm_connector *connector)
 {
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
 	struct edid *edid = (struct edid *)do_get_edid(encoder);
 	int n = 0;
 
 	if (edid) {
 		drm_mode_connector_update_edid_property(connector, edid);
 		n = drm_add_edid_modes(connector, edid);
+		priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
 		kfree(edid);
 	}
 
@@ -811,6 +1066,40 @@ tda998x_remove(struct i2c_client *client)
 	return 0;
 }
 
+static ssize_t i2c_read_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct drm_encoder *encoder = dev_get_drvdata(dev);
+	unsigned int page, addr;
+	unsigned char val;
+
+	sscanf(buf, "%x %x", &page, &addr);
+
+	val = reg_read(encoder, REG(page, addr));
+
+	printk("i2c read %02x @ page:%02x address:%02x\n", val, page, addr);
+	return size;
+}
+
+static ssize_t i2c_write_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct drm_encoder *encoder = dev_get_drvdata(dev);
+	unsigned int page, addr, mask, val;
+	unsigned char rval;
+
+	sscanf(buf, "%x %x %x %x", &page, &addr, &mask, &val);
+
+	rval = reg_read(encoder, REG(page, addr));
+	rval &= ~mask;
+	rval |= val & mask;
+	reg_write(encoder, REG(page, addr), rval);
+
+	printk("i2c write %02x @ page:%02x address:%02x\n", rval, page, addr);
+	return size;
+}
+
+static DEVICE_ATTR(i2c_read, S_IWUSR, NULL, i2c_read_store);
+static DEVICE_ATTR(i2c_write, S_IWUSR, NULL, i2c_write_store);
+
 static int
 tda998x_encoder_init(struct i2c_client *client,
 		    struct drm_device *dev,
@@ -818,6 +1107,11 @@ tda998x_encoder_init(struct i2c_client *client,
 {
 	struct drm_encoder *encoder = &encoder_slave->base;
 	struct tda998x_priv *priv;
+/* debug */
+	device_create_file(&client->dev, &dev_attr_i2c_read);
+	device_create_file(&client->dev, &dev_attr_i2c_write);
+	dev_set_drvdata(&client->dev, encoder);
+/* debug end */
 
 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 	if (!priv)
diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h
new file mode 100644
index 0000000..3e419d9
--- /dev/null
+++ b/include/drm/i2c/tda998x.h
@@ -0,0 +1,30 @@
+#ifndef __DRM_I2C_TDA998X_H__
+#define __DRM_I2C_TDA998X_H__
+
+struct tda998x_encoder_params {
+	u8 swap_b:3;
+	u8 mirr_b:1;
+	u8 swap_a:3;
+	u8 mirr_a:1;
+	u8 swap_d:3;
+	u8 mirr_d:1;
+	u8 swap_c:3;
+	u8 mirr_c:1;
+	u8 swap_f:3;
+	u8 mirr_f:1;
+	u8 swap_e:3;
+	u8 mirr_e:1;
+
+	u8 audio_cfg;
+	u8 audio_clk_cfg;
+	u8 audio_frame[6];
+
+	enum {
+		AFMT_SPDIF,
+		AFMT_I2S
+	} audio_format;
+
+	unsigned audio_sample_rate;
+};
+
+#endif
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 7/8] drm/i2c: nxp-tda998x: add video and audio input configuration
@ 2013-06-09 19:35   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:35 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/i2c/tda998x_drv.c |  308 ++++++++++++++++++++++++++++++++++++-
 include/drm/i2c/tda998x.h         |   30 ++++
 2 files changed, 331 insertions(+), 7 deletions(-)
 create mode 100644 include/drm/i2c/tda998x.h

diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index c0d17ae..ad773a6 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -23,7 +23,7 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_encoder_slave.h>
 #include <drm/drm_edid.h>
-
+#include <drm/i2c/tda998x.h>
 
 #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
 
@@ -32,9 +32,11 @@ struct tda998x_priv {
 	uint16_t rev;
 	uint8_t current_page;
 	int dpms;
+	bool is_hdmi_sink;
 	u8 vip_cntrl_0;
 	u8 vip_cntrl_1;
 	u8 vip_cntrl_2;
+	struct tda998x_encoder_params params;
 };
 
 #define to_tda998x_priv(x)  ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
@@ -71,10 +73,13 @@ struct tda998x_priv {
 # define I2C_MASTER_DIS_MM        (1 << 0)
 # define I2C_MASTER_DIS_FILT      (1 << 1)
 # define I2C_MASTER_APP_STRT_LAT  (1 << 2)
+#define REG_FEAT_POWERDOWN        REG(0x00, 0x0e)     /* read/write */
+# define FEAT_POWERDOWN_SPDIF     (1 << 3)
 #define REG_INT_FLAGS_0           REG(0x00, 0x0f)     /* read/write */
 #define REG_INT_FLAGS_1           REG(0x00, 0x10)     /* read/write */
 #define REG_INT_FLAGS_2           REG(0x00, 0x11)     /* read/write */
 # define INT_FLAGS_2_EDID_BLK_RD  (1 << 1)
+#define REG_ENA_ACLK              REG(0x00, 0x16)     /* read/write */
 #define REG_ENA_VP_0              REG(0x00, 0x18)     /* read/write */
 #define REG_ENA_VP_1              REG(0x00, 0x19)     /* read/write */
 #define REG_ENA_VP_2              REG(0x00, 0x1a)     /* read/write */
@@ -113,6 +118,7 @@ struct tda998x_priv {
 #define REG_VIP_CNTRL_5           REG(0x00, 0x25)     /* write */
 # define VIP_CNTRL_5_CKCASE       (1 << 0)
 # define VIP_CNTRL_5_SP_CNT(x)    (((x) & 3) << 1)
+#define REG_MUX_AP                REG(0x00, 0x26)     /* read/write */
 #define REG_MUX_VP_VIP_OUT        REG(0x00, 0x27)     /* read/write */
 #define REG_MAT_CONTRL            REG(0x00, 0x80)     /* write */
 # define MAT_CONTRL_MAT_SC(x)     (((x) & 3) << 0)
@@ -175,6 +181,12 @@ struct tda998x_priv {
 # define HVF_CNTRL_1_PAD(x)       (((x) & 3) << 4)
 # define HVF_CNTRL_1_SEMI_PLANAR  (1 << 6)
 #define REG_RPT_CNTRL             REG(0x00, 0xf0)     /* write */
+#define REG_I2S_FORMAT            REG(0x00, 0xfc)     /* read/write */
+# define I2S_FORMAT(x)            (((x) & 3) << 0)
+#define REG_AIP_CLKSEL            REG(0x00, 0xfd)     /* write */
+# define AIP_CLKSEL_FS(x)         (((x) & 3) << 0)
+# define AIP_CLKSEL_CLK_POL(x)    (((x) & 1) << 2)
+# define AIP_CLKSEL_AIP(x)        (((x) & 7) << 3)
 
 
 /* Page 02h: PLL settings */
@@ -216,6 +228,11 @@ struct tda998x_priv {
 
 
 /* Page 10h: information frames and packets */
+#define REG_IF1_HB0               REG(0x10, 0x20)     /* read/write */
+#define REG_IF2_HB0               REG(0x10, 0x40)     /* read/write */
+#define REG_IF3_HB0               REG(0x10, 0x60)     /* read/write */
+#define REG_IF4_HB0               REG(0x10, 0x80)     /* read/write */
+#define REG_IF5_HB0               REG(0x10, 0xa0)     /* read/write */
 
 
 /* Page 11h: audio settings and content info packets */
@@ -225,10 +242,33 @@ struct tda998x_priv {
 # define AIP_CNTRL_0_LAYOUT       (1 << 2)
 # define AIP_CNTRL_0_ACR_MAN      (1 << 5)
 # define AIP_CNTRL_0_RST_CTS      (1 << 6)
+#define REG_CA_I2S                REG(0x11, 0x01)     /* read/write */
+# define CA_I2S_CA_I2S(x)         (((x) & 31) << 0)
+# define CA_I2S_HBR_CHSTAT        (1 << 6)
+#define REG_LATENCY_RD            REG(0x11, 0x04)     /* read/write */
+#define REG_ACR_CTS_0             REG(0x11, 0x05)     /* read/write */
+#define REG_ACR_CTS_1             REG(0x11, 0x06)     /* read/write */
+#define REG_ACR_CTS_2             REG(0x11, 0x07)     /* read/write */
+#define REG_ACR_N_0               REG(0x11, 0x08)     /* read/write */
+#define REG_ACR_N_1               REG(0x11, 0x09)     /* read/write */
+#define REG_ACR_N_2               REG(0x11, 0x0a)     /* read/write */
+#define REG_CTS_N                 REG(0x11, 0x0c)     /* read/write */
+# define CTS_N_K(x)               (((x) & 7) << 0)
+# define CTS_N_M(x)               (((x) & 3) << 4)
 #define REG_ENC_CNTRL             REG(0x11, 0x0d)     /* read/write */
 # define ENC_CNTRL_RST_ENC        (1 << 0)
 # define ENC_CNTRL_RST_SEL        (1 << 1)
 # define ENC_CNTRL_CTL_CODE(x)    (((x) & 3) << 2)
+#define REG_DIP_FLAGS             REG(0x11, 0x0e)     /* read/write */
+# define DIP_FLAGS_ACR            (1 << 0)
+# define DIP_FLAGS_GC             (1 << 1)
+#define REG_DIP_IF_FLAGS          REG(0x11, 0x0f)     /* read/write */
+# define DIP_IF_FLAGS_IF1         (1 << 1)
+# define DIP_IF_FLAGS_IF2         (1 << 2)
+# define DIP_IF_FLAGS_IF3         (1 << 3)
+# define DIP_IF_FLAGS_IF4         (1 << 4)
+# define DIP_IF_FLAGS_IF5         (1 << 5)
+#define REG_CH_STAT_B(x)          REG(0x11, 0x14 + (x)) /* read/write */
 
 
 /* Page 12h: HDCP and OTP */
@@ -344,6 +384,23 @@ reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
 	return ret;
 }
 
+static void
+reg_write_range(struct drm_encoder *encoder, uint16_t reg, uint8_t *p, int cnt)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t buf[cnt+1];
+	int ret;
+
+	buf[0] = REG2ADDR(reg);
+	memcpy(&buf[1], p, cnt);
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, buf, cnt + 1);
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
 static uint8_t
 reg_read(struct drm_encoder *encoder, uint16_t reg)
 {
@@ -424,11 +481,192 @@ tda998x_reset(struct drm_encoder *encoder)
 	reg_write(encoder, REG_MUX_VP_VIP_OUT, 0x24);
 }
 
+static uint8_t tda998x_cksum(uint8_t *buf, size_t bytes)
+{
+	uint8_t sum = 0;
+
+	while (bytes--)
+		sum += *buf++;
+	return (255 - sum) + 1;
+}
+
+#define HB(x) (x)
+#define PB(x) (HB(2) + 1 + (x))
+
+static void
+tda998x_write_if(struct drm_encoder *encoder, uint8_t bit, uint16_t addr,
+		 uint8_t *buf, size_t size)
+{
+	buf[PB(0)] = tda998x_cksum(buf, size);
+
+	reg_clear(encoder, REG_DIP_IF_FLAGS, bit);
+	reg_write_range(encoder, addr, buf, size);
+	reg_set(encoder, REG_DIP_IF_FLAGS, bit);
+}
+
+static void
+tda998x_write_aif(struct drm_encoder *encoder, struct tda998x_encoder_params *p)
+{
+	uint8_t buf[PB(5) + 1];
+
+	buf[HB(0)] = 0x84;
+	buf[HB(1)] = 0x01;
+	buf[HB(2)] = 10;
+	buf[PB(0)] = 0;
+	buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */
+	buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */
+	buf[PB(4)] = p->audio_frame[4];
+	buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */
+
+	tda998x_write_if(encoder, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf,
+			 sizeof(buf));
+}
+
+static void
+tda998x_write_avi(struct drm_encoder *encoder, struct drm_display_mode *mode)
+{
+	uint8_t buf[PB(13) + 1];
+
+	memset(buf, 0, sizeof(buf));
+	buf[HB(0)] = 0x82;
+	buf[HB(1)] = 0x02;
+	buf[HB(2)] = 13;
+	buf[PB(4)] = drm_match_cea_mode(mode);
+
+	tda998x_write_if(encoder, DIP_IF_FLAGS_IF2, REG_IF2_HB0, buf,
+			 sizeof(buf));
+}
+
+static void tda998x_audio_mute(struct drm_encoder *encoder, bool on)
+{
+	if (on) {
+		reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO);
+		reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO);
+		reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+	} else {
+		reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+	}
+}
+
+static void
+tda998x_configure_audio(struct drm_encoder *encoder, struct tda998x_encoder_params *p)
+{
+	uint8_t buf[6], clksel_aip, clksel_fs, ca_i2s, cts_n;
+	uint32_t n;
+
+	/* SetAudioPortConfig */
+	reg_write(encoder, REG_ENA_AP, p->audio_cfg);
+	/* SetAudioClockPortConfig */
+	reg_write(encoder, REG_ENA_ACLK, p->audio_clk_cfg);
+
+	/*
+	 * layout = channelAllocation ? 1 : 0;
+	 * AudioInSetConfig(format, i2sFormat, channelAllocation,
+	 *   HDMITX_CHAN_NO_CHANGE, HDMITX_CLKPOLDSD_NO_CHANGE,
+	 *   HDMITX_SWAPDSD_NO_CHANGE, layout, HDMITX_LATENCY_CURRENT,
+	 *   dstRate)
+	 */
+	switch (p->audio_format) {
+	case AFMT_SPDIF:
+		reg_write(encoder, REG_MUX_AP, 0x40);
+		clksel_aip = AIP_CLKSEL_AIP(0);
+		/* FS64SPDIF */
+		clksel_fs = AIP_CLKSEL_FS(2);
+		cts_n = CTS_N_M(3) | CTS_N_K(3);
+		ca_i2s = 0;
+		break;
+
+	case AFMT_I2S:
+		reg_write(encoder, REG_MUX_AP, 0x64);
+		clksel_aip = AIP_CLKSEL_AIP(1);
+		/* ACLK */
+		clksel_fs = AIP_CLKSEL_FS(0);
+		cts_n = CTS_N_M(3) | CTS_N_K(3);
+		ca_i2s = CA_I2S_CA_I2S(0 /* channel allocation */);
+		break;
+	}
+
+	reg_write(encoder, REG_AIP_CLKSEL, clksel_aip);
+//	reg_write(encoder, REG_CA_I2S, ca_i2s);
+//	reg_write(encoder, REG_I2S_FORMAT, I2S_FORMAT(0 /* i2s format */));
+	reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT);
+	/* latency? */
+
+	/* get video format */
+
+	/*
+	 * ctsRef = HDMITX_CTSREF_FS64SPDIF, uCtsX = HDMITX_CTSX_64
+	 * AudioInSetCts(ctsRef, rate, VidFmt, vOutFreq,
+	 *   HDMITX_CTS_AUTO, uCtsX, HDMITX_CTSK_USE_CTSX,
+	 *   HDMITX_CTSMTS_USE_CTSX, dstRate)
+	 */
+	/* Auto CTS */
+	reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_ACR_MAN);
+	reg_write(encoder, REG_CTS_N, cts_n);
+	reg_write(encoder, REG_AUDIO_DIV, 3);
+
+	/*
+	 * This is the approximate value of N, which happens to be
+	 * the recommended values for non-coherent clocks.
+	 */
+	n = 128 * p->audio_sample_rate / 1000;
+
+	/* Write the CTS and N values */
+	buf[0] = 0x44;
+	buf[1] = 0x42;
+	buf[2] = 0x01;
+	buf[3] = n;
+	buf[4] = n >> 8;
+	buf[5] = n >> 16;
+	reg_write_range(encoder, REG_ACR_CTS_0, buf, 6);
+
+	/* Set CTS clock reference */
+	reg_write(encoder, REG_AIP_CLKSEL, clksel_aip | clksel_fs);
+
+	/* Reset CTS generator */
+	reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+	reg_clear(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+
+	/* Write the channel status */
+	buf[0] = 0x04;
+	buf[1] = 0x00;
+	buf[2] = 0x00;
+	buf[3] = 0xf1;
+	reg_write_range(encoder, REG_CH_STAT_B(0), buf, 4);
+
+	tda998x_audio_mute(encoder, true);
+	mdelay(20);
+	tda998x_audio_mute(encoder, false);
+
+	/* Write the audio information packet */
+	tda998x_write_aif(encoder, p);
+}
+
 /* DRM encoder functions */
 
 static void
 tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
 {
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+	struct tda998x_encoder_params *p = params;
+
+	priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
+			    (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
+			    VIP_CNTRL_0_SWAP_B(p->swap_b) |
+			    (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0);
+	priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) |
+			    (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) |
+			    VIP_CNTRL_1_SWAP_D(p->swap_d) |
+			    (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0);
+	priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) |
+			    (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) |
+			    VIP_CNTRL_2_SWAP_F(p->swap_f) |
+			    (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
+
+	priv->params = *p;
+
+	if (p->audio_cfg)
+		tda998x_configure_audio(encoder, p);
 }
 
 static void
@@ -446,7 +684,8 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 	switch (mode) {
 	case DRM_MODE_DPMS_ON:
 		/* enable audio and video ports */
-		reg_write(encoder, REG_ENA_AP, 0xff);
+//		reg_write(encoder, REG_ENA_AP, priv->ena_ap);
+//		reg_write(encoder, REG_ENA_ACLK, priv->ena_aclk);
 		reg_write(encoder, REG_ENA_VP_0, 0xff);
 		reg_write(encoder, REG_ENA_VP_1, 0xff);
 		reg_write(encoder, REG_ENA_VP_2, 0xff);
@@ -457,7 +696,7 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 		break;
 	case DRM_MODE_DPMS_OFF:
 		/* disable audio and video ports */
-		reg_write(encoder, REG_ENA_AP, 0x00);
+//		reg_write(encoder, REG_ENA_AP, 0x00);
 		reg_write(encoder, REG_ENA_VP_0, 0x00);
 		reg_write(encoder, REG_ENA_VP_1, 0x00);
 		reg_write(encoder, REG_ENA_VP_2, 0x00);
@@ -608,17 +847,31 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,
 	reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
 	reg_write16(encoder, REG_REFLINE_MSB, ref_line);
 
-	reg = TBG_CNTRL_1_VHX_EXT_DE |
-			TBG_CNTRL_1_VHX_EXT_HS |
-			TBG_CNTRL_1_VHX_EXT_VS |
-			TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
+	reg = TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
 			TBG_CNTRL_1_VH_TGL_2;
+	/*
+	 * It is questionable whether this is correct - the nxp driver
+	 * does not set VH_TGL_2 and the below for all display modes.
+	 */
 	if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
 		reg |= TBG_CNTRL_1_VH_TGL_0;
 	reg_set(encoder, REG_TBG_CNTRL_1, reg);
 
 	/* must be last register set: */
 	reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
+
+	/* Only setup the info frames if the sink is HDMI */
+	if (priv->is_hdmi_sink) {
+		/* We need to turn HDMI HDCP stuff on to get audio through */
+		reg_clear(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+		reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1));
+		reg_set(encoder, REG_TX33, TX33_HDMI);
+
+		tda998x_write_avi(encoder, adjusted_mode);
+
+		if (priv->params.audio_cfg)
+			tda998x_configure_audio(encoder, &priv->params);
+	}
 }
 
 static enum drm_connector_status
@@ -744,12 +997,14 @@ static int
 tda998x_encoder_get_modes(struct drm_encoder *encoder,
 			 struct drm_connector *connector)
 {
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
 	struct edid *edid = (struct edid *)do_get_edid(encoder);
 	int n = 0;
 
 	if (edid) {
 		drm_mode_connector_update_edid_property(connector, edid);
 		n = drm_add_edid_modes(connector, edid);
+		priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
 		kfree(edid);
 	}
 
@@ -811,6 +1066,40 @@ tda998x_remove(struct i2c_client *client)
 	return 0;
 }
 
+static ssize_t i2c_read_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct drm_encoder *encoder = dev_get_drvdata(dev);
+	unsigned int page, addr;
+	unsigned char val;
+
+	sscanf(buf, "%x %x", &page, &addr);
+
+	val = reg_read(encoder, REG(page, addr));
+
+	printk("i2c read %02x @ page:%02x address:%02x\n", val, page, addr);
+	return size;
+}
+
+static ssize_t i2c_write_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct drm_encoder *encoder = dev_get_drvdata(dev);
+	unsigned int page, addr, mask, val;
+	unsigned char rval;
+
+	sscanf(buf, "%x %x %x %x", &page, &addr, &mask, &val);
+
+	rval = reg_read(encoder, REG(page, addr));
+	rval &= ~mask;
+	rval |= val & mask;
+	reg_write(encoder, REG(page, addr), rval);
+
+	printk("i2c write %02x @ page:%02x address:%02x\n", rval, page, addr);
+	return size;
+}
+
+static DEVICE_ATTR(i2c_read, S_IWUSR, NULL, i2c_read_store);
+static DEVICE_ATTR(i2c_write, S_IWUSR, NULL, i2c_write_store);
+
 static int
 tda998x_encoder_init(struct i2c_client *client,
 		    struct drm_device *dev,
@@ -818,6 +1107,11 @@ tda998x_encoder_init(struct i2c_client *client,
 {
 	struct drm_encoder *encoder = &encoder_slave->base;
 	struct tda998x_priv *priv;
+/* debug */
+	device_create_file(&client->dev, &dev_attr_i2c_read);
+	device_create_file(&client->dev, &dev_attr_i2c_write);
+	dev_set_drvdata(&client->dev, encoder);
+/* debug end */
 
 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 	if (!priv)
diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h
new file mode 100644
index 0000000..3e419d9
--- /dev/null
+++ b/include/drm/i2c/tda998x.h
@@ -0,0 +1,30 @@
+#ifndef __DRM_I2C_TDA998X_H__
+#define __DRM_I2C_TDA998X_H__
+
+struct tda998x_encoder_params {
+	u8 swap_b:3;
+	u8 mirr_b:1;
+	u8 swap_a:3;
+	u8 mirr_a:1;
+	u8 swap_d:3;
+	u8 mirr_d:1;
+	u8 swap_c:3;
+	u8 mirr_c:1;
+	u8 swap_f:3;
+	u8 mirr_f:1;
+	u8 swap_e:3;
+	u8 mirr_e:1;
+
+	u8 audio_cfg;
+	u8 audio_clk_cfg;
+	u8 audio_frame[6];
+
+	enum {
+		AFMT_SPDIF,
+		AFMT_I2S
+	} audio_format;
+
+	unsigned audio_sample_rate;
+};
+
+#endif
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 8/8] DRM: Armada: add support for drm tda19988 driver
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:36   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:36 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/Kconfig      |    9 +++++++
 drivers/gpu/drm/armada/armada_drv.c |   42 +++++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
index c7a0a94..87e62dd 100644
--- a/drivers/gpu/drm/armada/Kconfig
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -13,3 +13,12 @@ config DRM_ARMADA
 	  This driver provides no built-in acceleration; acceleration is
 	  performed by other IP found on the SoC.  This driver provides
 	  kernel mode setting and buffer management to userspace.
+
+config DRM_ARMADA_TDA1998X
+	bool "Support TDA1998X HDMI output"
+	depends on DRM_ARMADA != n
+	depends on I2C && DRM_I2C_NXP_TDA998X = y
+	default y
+	help
+	  Support the TDA1998x HDMI output device found on the Solid-Run
+	  CuBox.
diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
index bb70cf5..c18ba0c 100644
--- a/drivers/gpu/drm/armada/armada_drv.c
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -16,6 +16,42 @@
 #include "armada_ioctl.h"
 #include "armada_ioctlP.h"
 
+#ifdef CONFIG_DRM_ARMADA_TDA1998X
+#include <drm/i2c/tda998x.h>
+#include "armada_slave.h"
+
+static struct tda998x_encoder_params params = {
+	/* With 0x24, there is no translation between vp_out and int_vp
+	FB	LCD out	Pins	VIP 	Int Vp
+	R:23:16	R:7:0	VPC7:0	7:0	7:0[R]
+	G:15:8	G:15:8	VPB7:0	23:16	23:16[G]
+	B:7:0	B:23:16	VPA7:0	15:8	15:8[B]
+	*/
+	.swap_a = 2,
+	.swap_b = 3,
+	.swap_c = 4,
+	.swap_d = 5,
+	.swap_e = 0,
+	.swap_f = 1,
+	.audio_cfg = BIT(2),
+	.audio_frame[1] = 1,
+	.audio_format = AFMT_SPDIF,
+	.audio_sample_rate = 44100,
+};
+
+static const struct armada_drm_slave_config tda19988_config = {
+	.i2c_adapter_id = 0,
+	.crtcs = 1 << 0, /* Only LCD0@the moment */
+	.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT,
+	.interlace_allowed = true,
+	.info = {
+		.type = "tda998x",
+		.addr = 0x70,
+		.platform_data = &params,
+	},
+};
+#endif
+
 static int armada_drm_load(struct drm_device *dev, unsigned long flags)
 {
 	struct armada_private *priv;
@@ -88,6 +124,12 @@ static int armada_drm_load(struct drm_device *dev, unsigned long flags)
 		}
 	}
 
+#ifdef CONFIG_DRM_ARMADA_TDA1998X
+	ret = armada_drm_connector_slave_create(dev, &tda19988_config);
+	if (ret)
+		goto err_kms;
+#endif
+
 	ret = drm_vblank_init(dev, n);
 	if (ret)
 		goto err_kms;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 8/8] DRM: Armada: add support for drm tda19988 driver
@ 2013-06-09 19:36   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-09 19:36 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/Kconfig      |    9 +++++++
 drivers/gpu/drm/armada/armada_drv.c |   42 +++++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
index c7a0a94..87e62dd 100644
--- a/drivers/gpu/drm/armada/Kconfig
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -13,3 +13,12 @@ config DRM_ARMADA
 	  This driver provides no built-in acceleration; acceleration is
 	  performed by other IP found on the SoC.  This driver provides
 	  kernel mode setting and buffer management to userspace.
+
+config DRM_ARMADA_TDA1998X
+	bool "Support TDA1998X HDMI output"
+	depends on DRM_ARMADA != n
+	depends on I2C && DRM_I2C_NXP_TDA998X = y
+	default y
+	help
+	  Support the TDA1998x HDMI output device found on the Solid-Run
+	  CuBox.
diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
index bb70cf5..c18ba0c 100644
--- a/drivers/gpu/drm/armada/armada_drv.c
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -16,6 +16,42 @@
 #include "armada_ioctl.h"
 #include "armada_ioctlP.h"
 
+#ifdef CONFIG_DRM_ARMADA_TDA1998X
+#include <drm/i2c/tda998x.h>
+#include "armada_slave.h"
+
+static struct tda998x_encoder_params params = {
+	/* With 0x24, there is no translation between vp_out and int_vp
+	FB	LCD out	Pins	VIP 	Int Vp
+	R:23:16	R:7:0	VPC7:0	7:0	7:0[R]
+	G:15:8	G:15:8	VPB7:0	23:16	23:16[G]
+	B:7:0	B:23:16	VPA7:0	15:8	15:8[B]
+	*/
+	.swap_a = 2,
+	.swap_b = 3,
+	.swap_c = 4,
+	.swap_d = 5,
+	.swap_e = 0,
+	.swap_f = 1,
+	.audio_cfg = BIT(2),
+	.audio_frame[1] = 1,
+	.audio_format = AFMT_SPDIF,
+	.audio_sample_rate = 44100,
+};
+
+static const struct armada_drm_slave_config tda19988_config = {
+	.i2c_adapter_id = 0,
+	.crtcs = 1 << 0, /* Only LCD0 at the moment */
+	.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT,
+	.interlace_allowed = true,
+	.info = {
+		.type = "tda998x",
+		.addr = 0x70,
+		.platform_data = &params,
+	},
+};
+#endif
+
 static int armada_drm_load(struct drm_device *dev, unsigned long flags)
 {
 	struct armada_private *priv;
@@ -88,6 +124,12 @@ static int armada_drm_load(struct drm_device *dev, unsigned long flags)
 		}
 	}
 
+#ifdef CONFIG_DRM_ARMADA_TDA1998X
+	ret = armada_drm_connector_slave_create(dev, &tda19988_config);
+	if (ret)
+		goto err_kms;
+#endif
+
 	ret = drm_vblank_init(dev, n);
 	if (ret)
 		goto err_kms;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [RFC v2 0/8] rmk's Dove DRM/TDA19988 Cubox driver
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-09 19:43   ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-09 19:43 UTC (permalink / raw)
  To: linux-arm-kernel

On Sun, Jun 09, 2013 at 08:06:12PM +0100, Russell King - ARM Linux wrote:
> This is version 2 of my DRM driver.  Changes since the previous version:

Okay, patch 1 got removed from this set, so some people won't see it
(it shouldn't have been sent).  Patch 2 is in moderation, which is
probably the more interesting patch because that's got the
interesting stuff in.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [RFC v2 0/8] rmk's Dove DRM/TDA19988 Cubox driver
@ 2013-06-09 19:43   ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-09 19:43 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

On Sun, Jun 09, 2013 at 08:06:12PM +0100, Russell King - ARM Linux wrote:
> This is version 2 of my DRM driver.  Changes since the previous version:

Okay, patch 1 got removed from this set, so some people won't see it
(it shouldn't have been sent).  Patch 2 is in moderation, which is
probably the more interesting patch because that's got the
interesting stuff in.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 5/8] drm/i2c: nxp-tda998x: fix npix/nline programming
  2013-06-09 19:32   ` Russell King
@ 2013-06-09 20:02     ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 120+ messages in thread
From: Sebastian Hesselbarth @ 2013-06-09 20:02 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/09/2013 09:32 PM, Russell King wrote:
> The npix/nline registers are supposed to be programmed with the total
> number of pixels/lines, not the displayed pixels/lines, and not minus
> one either.
>
> Signed-off-by: Russell King<rmk+kernel@arm.linux.org.uk>
> ---

Russell, Rob,

I have patches fixing TDA998x sync generation for progressive video.
They are based on TDA9983b datasheet [1]. TDA998x expects ITU-style
sync with positive hblank/vblank (or VESA-style hsync/vsync plus
refpix/refline). The patches make TDA998x behave as transparent as
possible, i.e. invert input sync signals as required and revert for
output sync as requested by EDID. I have measured a bunch of modes
with an oszilloscope near-end (TDA998x input) and far-end (DVI receiver
output) and they are pixel- and line-true.

Unfortunately, I have no receiver allowing me to also fix interlaced
sync generation but with progressive working, that shouldn't be that
hard to implement.

Will clean-up patches next week and rebase on Russell's RFC.

Sebastian

[1]
http://pdf1.alldatasheet.com/datasheet-pdf/view/347888/NXP/TDA9983B.html

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 5/8] drm/i2c: nxp-tda998x: fix npix/nline programming
@ 2013-06-09 20:02     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 120+ messages in thread
From: Sebastian Hesselbarth @ 2013-06-09 20:02 UTC (permalink / raw)
  To: Russell King
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, linux-arm-kernel

On 06/09/2013 09:32 PM, Russell King wrote:
> The npix/nline registers are supposed to be programmed with the total
> number of pixels/lines, not the displayed pixels/lines, and not minus
> one either.
>
> Signed-off-by: Russell King<rmk+kernel@arm.linux.org.uk>
> ---

Russell, Rob,

I have patches fixing TDA998x sync generation for progressive video.
They are based on TDA9983b datasheet [1]. TDA998x expects ITU-style
sync with positive hblank/vblank (or VESA-style hsync/vsync plus
refpix/refline). The patches make TDA998x behave as transparent as
possible, i.e. invert input sync signals as required and revert for
output sync as requested by EDID. I have measured a bunch of modes
with an oszilloscope near-end (TDA998x input) and far-end (DVI receiver
output) and they are pixel- and line-true.

Unfortunately, I have no receiver allowing me to also fix interlaced
sync generation but with progressive working, that shouldn't be that
hard to implement.

Will clean-up patches next week and rebase on Russell's RFC.

Sebastian

[1]
http://pdf1.alldatasheet.com/datasheet-pdf/view/347888/NXP/TDA9983B.html

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-09 19:32   ` Russell King
@ 2013-06-10 11:10     ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 120+ messages in thread
From: Sebastian Hesselbarth @ 2013-06-10 11:10 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/09/13 21:29, Russell King wrote:
> This patch adds support for the pair of LCD controllers on the Marvell
> Armada 510 SoCs.  This driver supports:
> - multiple contiguous scanout buffers for video and graphics
> - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>    acceleration
> - dual lcd0 and lcd1 crt operation
> - video overlay on each LCD crt
> - page flipping of the main scanout buffers
>
> Included in this commit is the core driver with no output support; output
> support is platform and encoder driver dependent.
>
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> ---
[...]
> diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> new file mode 100644
> index 0000000..e5ab4cb
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_crtc.c
> @@ -0,0 +1,766 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
[...]
> +static void armada_drm_crtc_update(struct armada_crtc *dcrtc)
> +{
> +	uint32_t dumb_ctrl;
> +
> +	dumb_ctrl = dcrtc->cfg_dumb_ctrl;
> +
> +	if (!dpms_blanked(dcrtc->dpms))
> +		dumb_ctrl |= CFG_DUMB_ENA;
> +
> +	/*
> +	 * When a dumb interface isn't under 24bit, it might be
> +	 * under SPI or GPIO.  If set to 7, this will force
> +	 * LCD_D[23:0] to output blank color and damage GPIO
> +	 * and SPI behaviour.  So leave it as-is unless in
> +	 * DUMB24_RGB888_0 mode.
> +	 */
> +	if (dpms_blanked(dcrtc->dpms) &&
> +	    (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
> +		dumb_ctrl &= ~DUMB_MASK;
> +		dumb_ctrl |= DUMB_BLANK;
> +	}
> +
> +	/*
> +	 * The spec is unclear about the polarities of the syncs.
> +	 * We assume their non-inverted state is active high.
> +	 */

nit: "We confirmed their non-inverted state is active high."

> +	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC)
> +		dumb_ctrl |= CFG_INV_CSYNC;
> +	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC)
> +		dumb_ctrl |= CFG_INV_HSYNC;
> +	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC)
> +		dumb_ctrl |= CFG_INV_VSYNC;
> +
> +	if (dcrtc->dumb_ctrl != dumb_ctrl) {
> +		dcrtc->dumb_ctrl = dumb_ctrl;
> +		writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL);
> +	}
> +}
[...]
> diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
> new file mode 100644
> index 0000000..bb70cf5
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_drv.c
> @@ -0,0 +1,326 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/clk.h>
> +#include <linux/module.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include "armada_crtc.h"
> +#include "armada_drm.h"
> +#include "armada_gem.h"
> +#include "armada_hw.h"
> +#include "armada_ioctl.h"
> +#include "armada_ioctlP.h"
> +
> +static int armada_drm_load(struct drm_device *dev, unsigned long flags)
> +{
> +	struct armada_private *priv;
> +	struct resource *res[ARRAY_SIZE(priv->dcrtc)];
> +	struct resource *mem = NULL;
> +	int ret, n, i;
> +
> +	memset(res, 0, sizeof(res));
> +
> +	for (n = i = 0; ; n++) {
> +		struct resource *r = platform_get_resource(dev->platformdev,
> +							   IORESOURCE_MEM, n);
> +		if (!r)
> +			break;
> +
> +		if (resource_size(r) > SZ_4K)
> +			mem = r;

nit again: The register address window of Dove LCD is 64k although you 
are right an no more than 512B are used. Also a comment would be nice,
that everything above 4k (64k) is interpreted as gfx mem.

> +		else if (i < ARRAY_SIZE(priv->dcrtc))
> +			res[i++] = r;
> +		else
> +			return -EINVAL;
> +	}
> +
> +	if (!res[0] || !mem)
> +		return -ENXIO;
> +
> +	if (!devm_request_mem_region(dev->dev, mem->start,
> +			resource_size(mem), "armada-drm"))
> +		return -EBUSY;
> +
> +	priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv) {
> +		DRM_ERROR("failed to allocate private\n");
> +		ret = -ENOMEM;
> +		goto err_buf;
> +	}
> +
> +	dev->dev_private = priv;
> +
> +	/* Mode setting support */
> +	drm_mode_config_init(dev);
> +	dev->mode_config.min_width = 0;
> +	dev->mode_config.min_height = 0;
> +	dev->mode_config.max_width = 2048;
> +	dev->mode_config.max_height = 2048;
> +	dev->mode_config.preferred_depth = 24;
> +	dev->mode_config.funcs = &armada_drm_mode_config_funcs;
> +	drm_mm_init(&priv->linear, mem->start, resource_size(mem));
> +
> +	/* Create all LCD controllers */
> +	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
> +		struct clk *clk;
> +
> +		if (!res[n])
> +			break;
> +
> +		clk = clk_get_sys("dovefb.0", "extclk");

To be precise: the above should have the index at extclk as there
is two extclk inputs that can be used for both lcdcs. So currently,
as armada_crtc is hard-coding extclk0 input it should be
"armadafb.%d", "extclk0".

But I know, clocking in general will work-out with parent select for
clk-mux and DT support.

[...]
> diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c
> new file mode 100644
> index 0000000..68c4efd
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_overlay.c
> @@ -0,0 +1,514 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
[...]
> +int armada_overlay_put_image_ioctl(struct drm_device *dev, void *data,
> +	struct drm_file *file)
> +{
> +	struct drm_armada_overlay_put_image *args = data;
> +	struct armada_private *priv = dev->dev_private;
> +	struct armada_overlay *ovl = priv->overlay;
> +	struct armada_gem_object *obj;
> +	struct armada_crtc *dcrtc;
> +	uint32_t stride_uv, stride_yc, src_hw, dst_hw, dst_yx;
> +	bool planar = false;
> +	int ret, idx;
> +
> +	if (!ovl) {
> +		DRM_DEBUG_DRIVER("no overlay");
> +		return -ENODEV;
> +	}
> +
> +	if (!(args->flags & ARMADA_OVERLAY_ENABLE)) {
> +		mutex_lock(&dev->mode_config.mutex);
> +		mutex_lock(&dev->struct_mutex);
> +
> +		armada_drm_overlay_off(dev, ovl);
> +
> +		mutex_unlock(&dev->struct_mutex);
> +		mutex_unlock(&dev->mode_config.mutex);
> +
> +		return 0;
> +	}
> +
> +//	DRM_DEBUG_DRIVER("flags %x handle %x src %dx%d dst %dx%d+%d+%d\n",
> +//		args->flags, args->bo_handle, args->src_scan_width, args->src_scan_height,
> +//		args->dst_width, args->dst_height, args->dst_x, args->dst_y);

nit: Reminder for a hopefully soon to surface non-RFC patch.

[...]
> diff --git a/drivers/gpu/drm/armada/armada_slave.c b/drivers/gpu/drm/armada/armada_slave.c
> new file mode 100644
> index 0000000..422a345
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_slave.c
> @@ -0,0 +1,138 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
[...]
> +static int armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
> +{
> +	const struct armada_drm_slave_config *config = data;
> +	struct drm_encoder_slave *slave;
> +	struct i2c_adapter *adap;
> +	int ret;
> +
> +	conn->interlace_allowed = config->interlace_allowed;
> +	conn->doublescan_allowed = config->doublescan_allowed;
> +	conn->polled = config->polled;
> +
> +	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
> +
> +	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
> +	if (!slave)
> +		return -ENOMEM;
> +
> +	slave->base.possible_crtcs = config->crtcs;
> +
> +	adap = i2c_get_adapter(config->i2c_adapter_id);
> +	if (!adap) {
> +		kfree(slave);
> +		return -EPROBE_DEFER;
> +	}
> +
> +	ret = drm_encoder_init(conn->dev, &slave->base,
> +			       &armada_drm_slave_encoder_funcs,
> +			       DRM_MODE_ENCODER_TMDS);
> +	if (ret) {
> +		DRM_ERROR("unable to init encoder\n");
> +		i2c_put_adapter(adap);
> +		kfree(slave);
> +		return ret;
> +	}
> +
> +	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
> +	i2c_put_adapter(adap);
> +	if (ret) {
> +		DRM_ERROR("unable to init encoder slave\n");
> +		armada_drm_slave_destroy(&slave->base);
> +		return ret;
> +	}
> +
> +	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
> +
> +	ret = slave->slave_funcs->create_resources(&slave->base, conn);
> +	if (ret) {
> +		armada_drm_slave_destroy(&slave->base);
> +		return ret;
> +	}
> +
> +	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
> +	if (ret) {
> +		armada_drm_slave_destroy(&slave->base);
> +		return ret;
> +	}
> +
> +	conn->encoder = &slave->base;
> +
> +	return ret;
> +}
> +
> +static const struct armada_output_type armada_drm_conn_slave = {
> +	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,

For a rework of DRM slave encoder API, there should also be some way to
get .connector_type and .encoder_type above from that slave encoder.
IMHO it should be up to the slave encoder to determine connector and
encoder type.

[...]
> diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
> new file mode 100644
> index 0000000..1b86696
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_slave.h
> @@ -0,0 +1,26 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef ARMADA_TDA19988_H
> +#define ARMADA_TDA19988_H

nit: ARMADA_SLAVE_H

> +
> +#include <linux/i2c.h>
> +#include <drm/drmP.h>
> +
> +struct armada_drm_slave_config {
> +	int i2c_adapter_id;
> +	uint32_t crtcs;
> +	uint8_t polled;
> +	bool interlace_allowed;
> +	bool doublescan_allowed;
> +	struct i2c_board_info info;
> +};
> +
> +int armada_drm_connector_slave_create(struct drm_device *dev,
> +	const struct armada_drm_slave_config *);
> +
> +#endif

Thanks again for sharing the driver as RFC! Will test soon and post
promised tda998x sync patches.

Sebastian

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 11:10     ` Sebastian Hesselbarth
  0 siblings, 0 replies; 120+ messages in thread
From: Sebastian Hesselbarth @ 2013-06-10 11:10 UTC (permalink / raw)
  To: Russell King
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, linux-arm-kernel

On 06/09/13 21:29, Russell King wrote:
> This patch adds support for the pair of LCD controllers on the Marvell
> Armada 510 SoCs.  This driver supports:
> - multiple contiguous scanout buffers for video and graphics
> - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>    acceleration
> - dual lcd0 and lcd1 crt operation
> - video overlay on each LCD crt
> - page flipping of the main scanout buffers
>
> Included in this commit is the core driver with no output support; output
> support is platform and encoder driver dependent.
>
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> ---
[...]
> diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> new file mode 100644
> index 0000000..e5ab4cb
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_crtc.c
> @@ -0,0 +1,766 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
[...]
> +static void armada_drm_crtc_update(struct armada_crtc *dcrtc)
> +{
> +	uint32_t dumb_ctrl;
> +
> +	dumb_ctrl = dcrtc->cfg_dumb_ctrl;
> +
> +	if (!dpms_blanked(dcrtc->dpms))
> +		dumb_ctrl |= CFG_DUMB_ENA;
> +
> +	/*
> +	 * When a dumb interface isn't under 24bit, it might be
> +	 * under SPI or GPIO.  If set to 7, this will force
> +	 * LCD_D[23:0] to output blank color and damage GPIO
> +	 * and SPI behaviour.  So leave it as-is unless in
> +	 * DUMB24_RGB888_0 mode.
> +	 */
> +	if (dpms_blanked(dcrtc->dpms) &&
> +	    (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
> +		dumb_ctrl &= ~DUMB_MASK;
> +		dumb_ctrl |= DUMB_BLANK;
> +	}
> +
> +	/*
> +	 * The spec is unclear about the polarities of the syncs.
> +	 * We assume their non-inverted state is active high.
> +	 */

nit: "We confirmed their non-inverted state is active high."

> +	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC)
> +		dumb_ctrl |= CFG_INV_CSYNC;
> +	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC)
> +		dumb_ctrl |= CFG_INV_HSYNC;
> +	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC)
> +		dumb_ctrl |= CFG_INV_VSYNC;
> +
> +	if (dcrtc->dumb_ctrl != dumb_ctrl) {
> +		dcrtc->dumb_ctrl = dumb_ctrl;
> +		writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL);
> +	}
> +}
[...]
> diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
> new file mode 100644
> index 0000000..bb70cf5
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_drv.c
> @@ -0,0 +1,326 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/clk.h>
> +#include <linux/module.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include "armada_crtc.h"
> +#include "armada_drm.h"
> +#include "armada_gem.h"
> +#include "armada_hw.h"
> +#include "armada_ioctl.h"
> +#include "armada_ioctlP.h"
> +
> +static int armada_drm_load(struct drm_device *dev, unsigned long flags)
> +{
> +	struct armada_private *priv;
> +	struct resource *res[ARRAY_SIZE(priv->dcrtc)];
> +	struct resource *mem = NULL;
> +	int ret, n, i;
> +
> +	memset(res, 0, sizeof(res));
> +
> +	for (n = i = 0; ; n++) {
> +		struct resource *r = platform_get_resource(dev->platformdev,
> +							   IORESOURCE_MEM, n);
> +		if (!r)
> +			break;
> +
> +		if (resource_size(r) > SZ_4K)
> +			mem = r;

nit again: The register address window of Dove LCD is 64k although you 
are right an no more than 512B are used. Also a comment would be nice,
that everything above 4k (64k) is interpreted as gfx mem.

> +		else if (i < ARRAY_SIZE(priv->dcrtc))
> +			res[i++] = r;
> +		else
> +			return -EINVAL;
> +	}
> +
> +	if (!res[0] || !mem)
> +		return -ENXIO;
> +
> +	if (!devm_request_mem_region(dev->dev, mem->start,
> +			resource_size(mem), "armada-drm"))
> +		return -EBUSY;
> +
> +	priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv) {
> +		DRM_ERROR("failed to allocate private\n");
> +		ret = -ENOMEM;
> +		goto err_buf;
> +	}
> +
> +	dev->dev_private = priv;
> +
> +	/* Mode setting support */
> +	drm_mode_config_init(dev);
> +	dev->mode_config.min_width = 0;
> +	dev->mode_config.min_height = 0;
> +	dev->mode_config.max_width = 2048;
> +	dev->mode_config.max_height = 2048;
> +	dev->mode_config.preferred_depth = 24;
> +	dev->mode_config.funcs = &armada_drm_mode_config_funcs;
> +	drm_mm_init(&priv->linear, mem->start, resource_size(mem));
> +
> +	/* Create all LCD controllers */
> +	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
> +		struct clk *clk;
> +
> +		if (!res[n])
> +			break;
> +
> +		clk = clk_get_sys("dovefb.0", "extclk");

To be precise: the above should have the index at extclk as there
is two extclk inputs that can be used for both lcdcs. So currently,
as armada_crtc is hard-coding extclk0 input it should be
"armadafb.%d", "extclk0".

But I know, clocking in general will work-out with parent select for
clk-mux and DT support.

[...]
> diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c
> new file mode 100644
> index 0000000..68c4efd
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_overlay.c
> @@ -0,0 +1,514 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
[...]
> +int armada_overlay_put_image_ioctl(struct drm_device *dev, void *data,
> +	struct drm_file *file)
> +{
> +	struct drm_armada_overlay_put_image *args = data;
> +	struct armada_private *priv = dev->dev_private;
> +	struct armada_overlay *ovl = priv->overlay;
> +	struct armada_gem_object *obj;
> +	struct armada_crtc *dcrtc;
> +	uint32_t stride_uv, stride_yc, src_hw, dst_hw, dst_yx;
> +	bool planar = false;
> +	int ret, idx;
> +
> +	if (!ovl) {
> +		DRM_DEBUG_DRIVER("no overlay");
> +		return -ENODEV;
> +	}
> +
> +	if (!(args->flags & ARMADA_OVERLAY_ENABLE)) {
> +		mutex_lock(&dev->mode_config.mutex);
> +		mutex_lock(&dev->struct_mutex);
> +
> +		armada_drm_overlay_off(dev, ovl);
> +
> +		mutex_unlock(&dev->struct_mutex);
> +		mutex_unlock(&dev->mode_config.mutex);
> +
> +		return 0;
> +	}
> +
> +//	DRM_DEBUG_DRIVER("flags %x handle %x src %dx%d dst %dx%d+%d+%d\n",
> +//		args->flags, args->bo_handle, args->src_scan_width, args->src_scan_height,
> +//		args->dst_width, args->dst_height, args->dst_x, args->dst_y);

nit: Reminder for a hopefully soon to surface non-RFC patch.

[...]
> diff --git a/drivers/gpu/drm/armada/armada_slave.c b/drivers/gpu/drm/armada/armada_slave.c
> new file mode 100644
> index 0000000..422a345
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_slave.c
> @@ -0,0 +1,138 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
[...]
> +static int armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
> +{
> +	const struct armada_drm_slave_config *config = data;
> +	struct drm_encoder_slave *slave;
> +	struct i2c_adapter *adap;
> +	int ret;
> +
> +	conn->interlace_allowed = config->interlace_allowed;
> +	conn->doublescan_allowed = config->doublescan_allowed;
> +	conn->polled = config->polled;
> +
> +	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
> +
> +	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
> +	if (!slave)
> +		return -ENOMEM;
> +
> +	slave->base.possible_crtcs = config->crtcs;
> +
> +	adap = i2c_get_adapter(config->i2c_adapter_id);
> +	if (!adap) {
> +		kfree(slave);
> +		return -EPROBE_DEFER;
> +	}
> +
> +	ret = drm_encoder_init(conn->dev, &slave->base,
> +			       &armada_drm_slave_encoder_funcs,
> +			       DRM_MODE_ENCODER_TMDS);
> +	if (ret) {
> +		DRM_ERROR("unable to init encoder\n");
> +		i2c_put_adapter(adap);
> +		kfree(slave);
> +		return ret;
> +	}
> +
> +	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
> +	i2c_put_adapter(adap);
> +	if (ret) {
> +		DRM_ERROR("unable to init encoder slave\n");
> +		armada_drm_slave_destroy(&slave->base);
> +		return ret;
> +	}
> +
> +	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
> +
> +	ret = slave->slave_funcs->create_resources(&slave->base, conn);
> +	if (ret) {
> +		armada_drm_slave_destroy(&slave->base);
> +		return ret;
> +	}
> +
> +	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
> +	if (ret) {
> +		armada_drm_slave_destroy(&slave->base);
> +		return ret;
> +	}
> +
> +	conn->encoder = &slave->base;
> +
> +	return ret;
> +}
> +
> +static const struct armada_output_type armada_drm_conn_slave = {
> +	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,

For a rework of DRM slave encoder API, there should also be some way to
get .connector_type and .encoder_type above from that slave encoder.
IMHO it should be up to the slave encoder to determine connector and
encoder type.

[...]
> diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
> new file mode 100644
> index 0000000..1b86696
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_slave.h
> @@ -0,0 +1,26 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef ARMADA_TDA19988_H
> +#define ARMADA_TDA19988_H

nit: ARMADA_SLAVE_H

> +
> +#include <linux/i2c.h>
> +#include <drm/drmP.h>
> +
> +struct armada_drm_slave_config {
> +	int i2c_adapter_id;
> +	uint32_t crtcs;
> +	uint8_t polled;
> +	bool interlace_allowed;
> +	bool doublescan_allowed;
> +	struct i2c_board_info info;
> +};
> +
> +int armada_drm_connector_slave_create(struct drm_device *dev,
> +	const struct armada_drm_slave_config *);
> +
> +#endif

Thanks again for sharing the driver as RFC! Will test soon and post
promised tda998x sync patches.

Sebastian

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-09 19:32   ` Russell King
@ 2013-06-10 15:57     ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 15:57 UTC (permalink / raw)
  To: linux-arm-kernel

On Sun, Jun 9, 2013 at 3:29 PM, Russell King
<rmk+kernel@arm.linux.org.uk> wrote:
> This patch adds support for the pair of LCD controllers on the Marvell
> Armada 510 SoCs.  This driver supports:
> - multiple contiguous scanout buffers for video and graphics
> - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>   acceleration
> - dual lcd0 and lcd1 crt operation
> - video overlay on each LCD crt

Any particular reason for not exposing the overlays as drm_plane's?
That is probably something that should change before merging the
driver.

Other than that, it looks reasonably good, with just a few (mostly
minor) comments below.

> - page flipping of the main scanout buffers
>
> Included in this commit is the core driver with no output support; output
> support is platform and encoder driver dependent.
>
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> ---

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> new file mode 100644
> index 0000000..e5ab4cb
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_crtc.c
> @@ -0,0 +1,766 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/clk.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include "armada_crtc.h"
> +#include "armada_drm.h"
> +#include "armada_fb.h"
> +#include "armada_gem.h"
> +#include "armada_hw.h"
> +
> +struct armada_frame_work {
> +       struct drm_pending_vblank_event *event;
> +       struct armada_regs regs[4];
> +       struct drm_gem_object *old_fb_obj;
> +       struct work_struct work;
> +};
> +
> +/*
> + * A note about interlacing.  Let's consider HDMI 1920x1080i.
> + * The timing parameters we have from X are:
> + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> + *  1920 2448 2492 2640  1080 1084 1094 1125
> + * Which get translated to:
> + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> + *  1920 2448 2492 2640   540  542  547  562
> + *
> + * This is how it is defined by CEA-861-D - line and pixel numbers are
> + * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
> + * line: 2640.  The odd frame, the first active line is at line 21, and
> + * the even frame, the first active line is 584.
> + *
> + * LN:    560     561     562     563             567     568    569
> + * DE:    ~~~|____________________________//__________________________
> + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
> + *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
> + *
> + * LN:    1123   1124    1125      1               5       6      7
> + * DE:    ~~~|____________________________//__________________________
> + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
> + *  23 blanking lines
> + *
> + * The Armada LCD Controller line and pixel numbers are, like X timings,
> + * referenced to the top left of the active frame.
> + *
> + * So, translating these to our LCD controller:
> + *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
> + *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
> + * Note: Vsync front porch remains constant!
> + *
> + * if (odd_frame) {
> + *   vtotal = mode->crtc_vtotal + 1;
> + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
> + *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
> + * } else {
> + *   vtotal = mode->crtc_vtotal;
> + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
> + *   vhorizpos = mode->crtc_hsync_start;
> + * }
> + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
> + *
> + * So, we need to reprogram these registers on each vsync event:
> + *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL

ouch, proof that some hw designers must really hate driver writers ;-)

[snip]

> +/* The mode_config.mutex will be held for this call */
> +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> +       struct drm_framebuffer *old_fb)
> +{
> +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> +       struct armada_regs regs[4];
> +       unsigned i;
> +
> +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
> +       armada_reg_queue_end(regs, i);
> +
> +       /* Wait for pending flips to complete */
> +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
> +
> +       /* Take a reference to the new fb as we're using it */
> +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);

note that you probably want to ref/unref the fb (and let the fb hold a
ref to the gem bo).. that will make life easier for planar formats too
(as the fb should hold ref's to the bo for each plane)

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
[snip]
> +
> +static struct drm_ioctl_desc armada_ioctls[] = {
> +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
> +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),

you might want just a single ioctl for creating gem objects, with a
'scanout' flag..  perhaps some future version of the hw doesn't need
phys contig for scanout, and this would probably be easier to handle
with a single ioctl that has an 'if (flags & SCANOUT &&
device_needs_phys_contig_scanout()) .. '

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
[snip]
> +int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
> +{
> +       struct armada_private *priv = dev->dev_private;
> +       size_t size = obj->obj.size;
> +
> +       if (obj->page || obj->linear)
> +               return 0;
> +
> +       /* If it is a small allocation, try to get it from the system */
> +       if (size < 1048576) {
> +               unsigned int order = get_order(size);
> +               struct page *p = alloc_pages(GFP_KERNEL, order);
> +
> +               if (p) {
> +                       unsigned sz = (size + 31) & ~31;
> +                       uintptr_t ptr;
> +
> +                       obj->phys_addr = page_to_phys(p);
> +                       obj->dev_addr = obj->phys_addr;
> +
> +                       /* FIXME: Hack around dirty cache */
> +                       ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
> +                       memset((void *)ptr, 0, PAGE_ALIGN(size));
> +                       while (sz) {
> +                               asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
> +                               ptr += 32;
> +                               sz -= 32;
> +                       }
> +                       dsb();
> +
> +                       obj->page = p;
> +               }
> +       }

maybe leave out the small size case until there is a better way to
deal with cache, since this seems like just an optimization..

although I wonder why not just use CMA?  (Other than maybe
performance.. but I guess this is only for allocating scanout buffers,
in which case all the bazillion of temporary pixmaps that X11
creates/deletes won't be hitting this path..)

> +       /* Otherwise, grab it from our linear allocation */
> +       if (!obj->page) {
> +               struct drm_mm_node *free;
> +               unsigned align = min_t(unsigned, size, SZ_2M);
> +               void __iomem *ptr;
> +
> +               free = drm_mm_search_free(&priv->linear, size, align, 0);
> +               if (free)
> +                       obj->linear = drm_mm_get_block(free, size, align);
> +               if (!obj->linear)
> +                       return -ENOSPC;
> +
> +               /* Ensure that the memory we're returning is cleared. */
> +               ptr = ioremap_wc(obj->linear->start, size);
> +               if (!ptr) {
> +                       drm_mm_put_block(obj->linear);
> +                       obj->linear = NULL;
> +                       return -ENOMEM;
> +               }
> +
> +               memset_io(ptr, 0, size);
> +               iounmap(ptr);
> +
> +               obj->phys_addr = obj->linear->start;
> +               obj->dev_addr = obj->linear->start;
> +       }
> +
> +       DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n",
> +                        obj, obj->phys_addr, obj->dev_addr);
> +
> +       return 0;
> +}

[snip]

> +int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
> +       struct drm_file *file)
> +{
> +       struct drm_armada_gem_prop *arg = data;
> +       struct armada_gem_object *dobj;
> +       int ret;
> +
> +       ret = mutex_lock_interruptible(&dev->struct_mutex);
> +       if (ret)
> +               return ret;
> +
> +       dobj = armada_gem_object_lookup(dev, file, arg->handle);
> +       if (dobj == NULL) {
> +               ret = -ENOENT;
> +               goto unlock;
> +       }
> +
> +       arg->phys = dobj->phys_addr;

ugg, do you really need to expose phys addr to userspace?  Any chance
to just teach the gpu bits about dmabuf instead?

> +       ret = 0;
> +       drm_gem_object_unreference(&dobj->obj);
> +
> + unlock:
> +       mutex_unlock(&dev->struct_mutex);
> +
> +       return ret;
> +}

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_ioctl.h b/drivers/gpu/drm/armada/armada_ioctl.h
> new file mode 100644
> index 0000000..619ec2c
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_ioctl.h
> @@ -0,0 +1,128 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  With inspiration from the i915 driver
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef DRM_ARMADA_IOCTL_H
> +#define DRM_ARMADA_IOCTL_H
> +
> +#define DRM_ARMADA_GEM_CREATE          0x00
> +#define DRM_ARMADA_GEM_CREATE_PHYS     0x01
> +#define DRM_ARMADA_GEM_MMAP            0x02
> +#define DRM_ARMADA_GEM_PWRITE          0x03
> +#define DRM_ARMADA_GEM_PROP            0x04
> +#define DRM_ARMADA_OVERLAY_PUT_IMAGE   0x06
> +#define DRM_ARMADA_OVERLAY_ATTRS               0x07
> +
> +#define ARMADA_IOCTL(dir,name,str) \
> +       DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
> +
> +struct drm_armada_gem_create {
> +       uint32_t height;
> +       uint32_t width;
> +       uint32_t bpp;

just fwiw, typically height/width/bpp are properties of the fb but not
the bo.. (except in some cases where kernel needs to know this to
setup GTT correctly for tiled buffers)

> +       uint32_t handle;
> +       uint32_t pitch;
> +       uint32_t size;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_CREATE \
> +       ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
> +
> +struct drm_armada_gem_create_phys {
> +       uint32_t size;
> +       uint32_t handle;
> +       uint64_t phys;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
> +       ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
> +
> +struct drm_armada_gem_mmap {
> +       uint32_t handle;
> +       uint32_t pad;
> +       uint64_t offset;
> +       uint64_t size;
> +       uint64_t addr;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_MMAP \
> +       ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
> +
> +struct drm_armada_gem_pwrite {
> +       uint32_t handle;
> +       uint32_t offset;
> +       uint32_t size;

probably want a uint32_t padding here, or move the uint64_t up in the
struct to avoid 32 vs 64b alignment differences.

> +       uint64_t ptr;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_PWRITE \
> +       ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
> +

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_output.c b/drivers/gpu/drm/armada/armada_output.c

[snip]

> +/* Shouldn't this be a generic helper function? */

I didn't bother making it a generic helper, because in tilcdc I also
needed to check some crtc constraints.  Although if there is >1 other
drivers that don't need to, we can easily make it a helper.

> +int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
> +       struct drm_display_mode *mode)
> +{
> +       struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
> +       int valid = MODE_BAD;
> +
> +       if (encoder) {
> +               struct drm_encoder_slave *slave = to_encoder_slave(encoder);
> +
> +               valid = slave->slave_funcs->mode_valid(encoder, mode);
> +       }
> +       return valid;
> +}
> +

[snip]

> diff --git a/drivers/gpu/drm/armada/drm_helper.h b/drivers/gpu/drm/armada/drm_helper.h
> new file mode 100644
> index 0000000..d9f2e8d
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/drm_helper.h
> @@ -0,0 +1,31 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef DRM_HELPER_H
> +#define DRM_HELPER_H
> +
> +/* Useful helpers I wish the DRM core would provide */
> +

yeah, we should probably just add these in core

BR,
-R

> +#include <drm/drmP.h>
> +
> +static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
> +       uint32_t id)
> +{
> +       struct drm_mode_object *mo;
> +       mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
> +       return mo ? obj_to_crtc(mo) : NULL;
> +}
> +
> +static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
> +       uint32_t id)
> +{
> +       struct drm_mode_object *mo;
> +       mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
> +       return mo ? obj_to_encoder(mo) : NULL;
> +}
> +
> +#endif
> --
> 1.7.4.4
>

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 15:57     ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 15:57 UTC (permalink / raw)
  To: Russell King
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Sun, Jun 9, 2013 at 3:29 PM, Russell King
<rmk+kernel@arm.linux.org.uk> wrote:
> This patch adds support for the pair of LCD controllers on the Marvell
> Armada 510 SoCs.  This driver supports:
> - multiple contiguous scanout buffers for video and graphics
> - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>   acceleration
> - dual lcd0 and lcd1 crt operation
> - video overlay on each LCD crt

Any particular reason for not exposing the overlays as drm_plane's?
That is probably something that should change before merging the
driver.

Other than that, it looks reasonably good, with just a few (mostly
minor) comments below.

> - page flipping of the main scanout buffers
>
> Included in this commit is the core driver with no output support; output
> support is platform and encoder driver dependent.
>
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> ---

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> new file mode 100644
> index 0000000..e5ab4cb
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_crtc.c
> @@ -0,0 +1,766 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  Rewritten from the dovefb driver, and Armada510 manuals.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/clk.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include "armada_crtc.h"
> +#include "armada_drm.h"
> +#include "armada_fb.h"
> +#include "armada_gem.h"
> +#include "armada_hw.h"
> +
> +struct armada_frame_work {
> +       struct drm_pending_vblank_event *event;
> +       struct armada_regs regs[4];
> +       struct drm_gem_object *old_fb_obj;
> +       struct work_struct work;
> +};
> +
> +/*
> + * A note about interlacing.  Let's consider HDMI 1920x1080i.
> + * The timing parameters we have from X are:
> + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> + *  1920 2448 2492 2640  1080 1084 1094 1125
> + * Which get translated to:
> + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> + *  1920 2448 2492 2640   540  542  547  562
> + *
> + * This is how it is defined by CEA-861-D - line and pixel numbers are
> + * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
> + * line: 2640.  The odd frame, the first active line is at line 21, and
> + * the even frame, the first active line is 584.
> + *
> + * LN:    560     561     562     563             567     568    569
> + * DE:    ~~~|____________________________//__________________________
> + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
> + *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
> + *
> + * LN:    1123   1124    1125      1               5       6      7
> + * DE:    ~~~|____________________________//__________________________
> + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
> + *  23 blanking lines
> + *
> + * The Armada LCD Controller line and pixel numbers are, like X timings,
> + * referenced to the top left of the active frame.
> + *
> + * So, translating these to our LCD controller:
> + *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
> + *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
> + * Note: Vsync front porch remains constant!
> + *
> + * if (odd_frame) {
> + *   vtotal = mode->crtc_vtotal + 1;
> + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
> + *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
> + * } else {
> + *   vtotal = mode->crtc_vtotal;
> + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
> + *   vhorizpos = mode->crtc_hsync_start;
> + * }
> + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
> + *
> + * So, we need to reprogram these registers on each vsync event:
> + *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL

ouch, proof that some hw designers must really hate driver writers ;-)

[snip]

> +/* The mode_config.mutex will be held for this call */
> +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> +       struct drm_framebuffer *old_fb)
> +{
> +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> +       struct armada_regs regs[4];
> +       unsigned i;
> +
> +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
> +       armada_reg_queue_end(regs, i);
> +
> +       /* Wait for pending flips to complete */
> +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
> +
> +       /* Take a reference to the new fb as we're using it */
> +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);

note that you probably want to ref/unref the fb (and let the fb hold a
ref to the gem bo).. that will make life easier for planar formats too
(as the fb should hold ref's to the bo for each plane)

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
[snip]
> +
> +static struct drm_ioctl_desc armada_ioctls[] = {
> +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
> +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),

you might want just a single ioctl for creating gem objects, with a
'scanout' flag..  perhaps some future version of the hw doesn't need
phys contig for scanout, and this would probably be easier to handle
with a single ioctl that has an 'if (flags & SCANOUT &&
device_needs_phys_contig_scanout()) .. '

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
[snip]
> +int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
> +{
> +       struct armada_private *priv = dev->dev_private;
> +       size_t size = obj->obj.size;
> +
> +       if (obj->page || obj->linear)
> +               return 0;
> +
> +       /* If it is a small allocation, try to get it from the system */
> +       if (size < 1048576) {
> +               unsigned int order = get_order(size);
> +               struct page *p = alloc_pages(GFP_KERNEL, order);
> +
> +               if (p) {
> +                       unsigned sz = (size + 31) & ~31;
> +                       uintptr_t ptr;
> +
> +                       obj->phys_addr = page_to_phys(p);
> +                       obj->dev_addr = obj->phys_addr;
> +
> +                       /* FIXME: Hack around dirty cache */
> +                       ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
> +                       memset((void *)ptr, 0, PAGE_ALIGN(size));
> +                       while (sz) {
> +                               asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
> +                               ptr += 32;
> +                               sz -= 32;
> +                       }
> +                       dsb();
> +
> +                       obj->page = p;
> +               }
> +       }

maybe leave out the small size case until there is a better way to
deal with cache, since this seems like just an optimization..

although I wonder why not just use CMA?  (Other than maybe
performance.. but I guess this is only for allocating scanout buffers,
in which case all the bazillion of temporary pixmaps that X11
creates/deletes won't be hitting this path..)

> +       /* Otherwise, grab it from our linear allocation */
> +       if (!obj->page) {
> +               struct drm_mm_node *free;
> +               unsigned align = min_t(unsigned, size, SZ_2M);
> +               void __iomem *ptr;
> +
> +               free = drm_mm_search_free(&priv->linear, size, align, 0);
> +               if (free)
> +                       obj->linear = drm_mm_get_block(free, size, align);
> +               if (!obj->linear)
> +                       return -ENOSPC;
> +
> +               /* Ensure that the memory we're returning is cleared. */
> +               ptr = ioremap_wc(obj->linear->start, size);
> +               if (!ptr) {
> +                       drm_mm_put_block(obj->linear);
> +                       obj->linear = NULL;
> +                       return -ENOMEM;
> +               }
> +
> +               memset_io(ptr, 0, size);
> +               iounmap(ptr);
> +
> +               obj->phys_addr = obj->linear->start;
> +               obj->dev_addr = obj->linear->start;
> +       }
> +
> +       DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n",
> +                        obj, obj->phys_addr, obj->dev_addr);
> +
> +       return 0;
> +}

[snip]

> +int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
> +       struct drm_file *file)
> +{
> +       struct drm_armada_gem_prop *arg = data;
> +       struct armada_gem_object *dobj;
> +       int ret;
> +
> +       ret = mutex_lock_interruptible(&dev->struct_mutex);
> +       if (ret)
> +               return ret;
> +
> +       dobj = armada_gem_object_lookup(dev, file, arg->handle);
> +       if (dobj == NULL) {
> +               ret = -ENOENT;
> +               goto unlock;
> +       }
> +
> +       arg->phys = dobj->phys_addr;

ugg, do you really need to expose phys addr to userspace?  Any chance
to just teach the gpu bits about dmabuf instead?

> +       ret = 0;
> +       drm_gem_object_unreference(&dobj->obj);
> +
> + unlock:
> +       mutex_unlock(&dev->struct_mutex);
> +
> +       return ret;
> +}

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_ioctl.h b/drivers/gpu/drm/armada/armada_ioctl.h
> new file mode 100644
> index 0000000..619ec2c
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/armada_ioctl.h
> @@ -0,0 +1,128 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *  With inspiration from the i915 driver
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef DRM_ARMADA_IOCTL_H
> +#define DRM_ARMADA_IOCTL_H
> +
> +#define DRM_ARMADA_GEM_CREATE          0x00
> +#define DRM_ARMADA_GEM_CREATE_PHYS     0x01
> +#define DRM_ARMADA_GEM_MMAP            0x02
> +#define DRM_ARMADA_GEM_PWRITE          0x03
> +#define DRM_ARMADA_GEM_PROP            0x04
> +#define DRM_ARMADA_OVERLAY_PUT_IMAGE   0x06
> +#define DRM_ARMADA_OVERLAY_ATTRS               0x07
> +
> +#define ARMADA_IOCTL(dir,name,str) \
> +       DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
> +
> +struct drm_armada_gem_create {
> +       uint32_t height;
> +       uint32_t width;
> +       uint32_t bpp;

just fwiw, typically height/width/bpp are properties of the fb but not
the bo.. (except in some cases where kernel needs to know this to
setup GTT correctly for tiled buffers)

> +       uint32_t handle;
> +       uint32_t pitch;
> +       uint32_t size;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_CREATE \
> +       ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
> +
> +struct drm_armada_gem_create_phys {
> +       uint32_t size;
> +       uint32_t handle;
> +       uint64_t phys;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
> +       ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
> +
> +struct drm_armada_gem_mmap {
> +       uint32_t handle;
> +       uint32_t pad;
> +       uint64_t offset;
> +       uint64_t size;
> +       uint64_t addr;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_MMAP \
> +       ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
> +
> +struct drm_armada_gem_pwrite {
> +       uint32_t handle;
> +       uint32_t offset;
> +       uint32_t size;

probably want a uint32_t padding here, or move the uint64_t up in the
struct to avoid 32 vs 64b alignment differences.

> +       uint64_t ptr;
> +};
> +#define DRM_IOCTL_ARMADA_GEM_PWRITE \
> +       ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
> +

[snip]

> diff --git a/drivers/gpu/drm/armada/armada_output.c b/drivers/gpu/drm/armada/armada_output.c

[snip]

> +/* Shouldn't this be a generic helper function? */

I didn't bother making it a generic helper, because in tilcdc I also
needed to check some crtc constraints.  Although if there is >1 other
drivers that don't need to, we can easily make it a helper.

> +int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
> +       struct drm_display_mode *mode)
> +{
> +       struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
> +       int valid = MODE_BAD;
> +
> +       if (encoder) {
> +               struct drm_encoder_slave *slave = to_encoder_slave(encoder);
> +
> +               valid = slave->slave_funcs->mode_valid(encoder, mode);
> +       }
> +       return valid;
> +}
> +

[snip]

> diff --git a/drivers/gpu/drm/armada/drm_helper.h b/drivers/gpu/drm/armada/drm_helper.h
> new file mode 100644
> index 0000000..d9f2e8d
> --- /dev/null
> +++ b/drivers/gpu/drm/armada/drm_helper.h
> @@ -0,0 +1,31 @@
> +/*
> + * Copyright (C) 2012 Russell King
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef DRM_HELPER_H
> +#define DRM_HELPER_H
> +
> +/* Useful helpers I wish the DRM core would provide */
> +

yeah, we should probably just add these in core

BR,
-R

> +#include <drm/drmP.h>
> +
> +static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
> +       uint32_t id)
> +{
> +       struct drm_mode_object *mo;
> +       mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
> +       return mo ? obj_to_crtc(mo) : NULL;
> +}
> +
> +static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
> +       uint32_t id)
> +{
> +       struct drm_mode_object *mo;
> +       mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
> +       return mo ? obj_to_encoder(mo) : NULL;
> +}
> +
> +#endif
> --
> 1.7.4.4
>

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 15:57     ` Rob Clark
@ 2013-06-10 17:06       ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 17:06 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> <rmk+kernel@arm.linux.org.uk> wrote:
> > This patch adds support for the pair of LCD controllers on the Marvell
> > Armada 510 SoCs.  This driver supports:
> > - multiple contiguous scanout buffers for video and graphics
> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> >   acceleration
> > - dual lcd0 and lcd1 crt operation
> > - video overlay on each LCD crt
> 
> Any particular reason for not exposing the overlays as drm_plane's?
> That is probably something that should change before merging the
> driver.

Only through not understanding much of DRM when I started this.
Provided DRM planes can do everything that I'm already doing with
the overlay support, then yes.  Otherwise, I want to stick with this
which is modelled after the i915 overlay interface.

The big question that this brings up is that the plane stuff looks
a lot more heavyweight in terms of the computation done.  I'm already
concerned that I'm doing too much with the overlay code - it occasionally
misses getting the next frame of video ready before the VBLANK event
in certain circumstances which I haven't been able to quantify.  I'd
rather avoid adding too much additional work here, which would then
make overlay pretty useless for what it's meant for - video playback.

... and it looks to me like it can't - because I sometimes get blocks
of physical memory with the YUV data already in appropriately formatted
for scanout that I really would like to avoid having to expensively
copy.

> > - page flipping of the main scanout buffers
> >
> > Included in this commit is the core driver with no output support; output
> > support is platform and encoder driver dependent.
> >
> > Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> > ---
> 
> [snip]
> 
> > diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> > new file mode 100644
> > index 0000000..e5ab4cb
> > --- /dev/null
> > +++ b/drivers/gpu/drm/armada/armada_crtc.c
> > @@ -0,0 +1,766 @@
> > +/*
> > + * Copyright (C) 2012 Russell King
> > + *  Rewritten from the dovefb driver, and Armada510 manuals.
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +#include <linux/clk.h>
> > +#include <drm/drmP.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include "armada_crtc.h"
> > +#include "armada_drm.h"
> > +#include "armada_fb.h"
> > +#include "armada_gem.h"
> > +#include "armada_hw.h"
> > +
> > +struct armada_frame_work {
> > +       struct drm_pending_vblank_event *event;
> > +       struct armada_regs regs[4];
> > +       struct drm_gem_object *old_fb_obj;
> > +       struct work_struct work;
> > +};
> > +
> > +/*
> > + * A note about interlacing.  Let's consider HDMI 1920x1080i.
> > + * The timing parameters we have from X are:
> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> > + *  1920 2448 2492 2640  1080 1084 1094 1125
> > + * Which get translated to:
> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> > + *  1920 2448 2492 2640   540  542  547  562
> > + *
> > + * This is how it is defined by CEA-861-D - line and pixel numbers are
> > + * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
> > + * line: 2640.  The odd frame, the first active line is at line 21, and
> > + * the even frame, the first active line is 584.
> > + *
> > + * LN:    560     561     562     563             567     568    569
> > + * DE:    ~~~|____________________________//__________________________
> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> > + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
> > + *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
> > + *
> > + * LN:    1123   1124    1125      1               5       6      7
> > + * DE:    ~~~|____________________________//__________________________
> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> > + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
> > + *  23 blanking lines
> > + *
> > + * The Armada LCD Controller line and pixel numbers are, like X timings,
> > + * referenced to the top left of the active frame.
> > + *
> > + * So, translating these to our LCD controller:
> > + *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
> > + *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
> > + * Note: Vsync front porch remains constant!
> > + *
> > + * if (odd_frame) {
> > + *   vtotal = mode->crtc_vtotal + 1;
> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
> > + *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
> > + * } else {
> > + *   vtotal = mode->crtc_vtotal;
> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
> > + *   vhorizpos = mode->crtc_hsync_start;
> > + * }
> > + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
> > + *
> > + * So, we need to reprogram these registers on each vsync event:
> > + *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
> 
> ouch, proof that some hw designers must really hate driver writers ;-)

Yep - and interlace support is not something that can be done in a stable
way with the way the Linux kernel deals with interrupts (iow, with no
priority and strictly single-threaded.)  The problem is to do this
properly, you need the LCD interrupt able to interrupt any other interrupt
handler so you can reprogram the LCD controller within a very tight
timeframe (less than one display field.)

It works, but occasionally you will get a hiccup (mainly that's the
fault of the USB stack taking 2.25ms over some of its keyboard/mouse
interrupts!)

> > +/* The mode_config.mutex will be held for this call */
> > +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> > +       struct drm_framebuffer *old_fb)
> > +{
> > +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> > +       struct armada_regs regs[4];
> > +       unsigned i;
> > +
> > +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
> > +       armada_reg_queue_end(regs, i);
> > +
> > +       /* Wait for pending flips to complete */
> > +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
> > +
> > +       /* Take a reference to the new fb as we're using it */
> > +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
> 
> note that you probably want to ref/unref the fb (and let the fb hold a
> ref to the gem bo).. that will make life easier for planar formats too
> (as the fb should hold ref's to the bo for each plane)

I'll look into it, but it will take some time as the refcounting is far
from trivial or obvious in DRM...

>From what I can see there's no intrinsic refcounting between the fb
and the gem bo.

> > diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
> [snip]
> > +
> > +static struct drm_ioctl_desc armada_ioctls[] = {
> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
> 
> you might want just a single ioctl for creating gem objects, with a
> 'scanout' flag..  perhaps some future version of the hw doesn't need
> phys contig for scanout, and this would probably be easier to handle
> with a single ioctl that has an 'if (flags & SCANOUT &&
> device_needs_phys_contig_scanout()) .. '

The purposes of these two ioctls are quite different.  ARMADA_GEM_CREATE
is to create any gem buffer object that needs shmem backing - eg,
non-scanout pixmaps and the like.

ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
This allows my X server to remain compatible with the XF86 Dove driver,
which permits the passing of the physical address of the video frame to
the X server (otherwise we're into rewriting a whole load more code than
is really desirable - and I really don't have time to rewrite bits of
gstreamer and other stuff.)

Lastly, the DUMB stuff is used for the graphics scanout buffer.

> > diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
> [snip]
> > +int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
> > +{
> > +       struct armada_private *priv = dev->dev_private;
> > +       size_t size = obj->obj.size;
> > +
> > +       if (obj->page || obj->linear)
> > +               return 0;
> > +
> > +       /* If it is a small allocation, try to get it from the system */
> > +       if (size < 1048576) {
> > +               unsigned int order = get_order(size);
> > +               struct page *p = alloc_pages(GFP_KERNEL, order);
> > +
> > +               if (p) {
> > +                       unsigned sz = (size + 31) & ~31;
> > +                       uintptr_t ptr;
> > +
> > +                       obj->phys_addr = page_to_phys(p);
> > +                       obj->dev_addr = obj->phys_addr;
> > +
> > +                       /* FIXME: Hack around dirty cache */
> > +                       ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
> > +                       memset((void *)ptr, 0, PAGE_ALIGN(size));
> > +                       while (sz) {
> > +                               asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
> > +                               ptr += 32;
> > +                               sz -= 32;
> > +                       }
> > +                       dsb();
> > +
> > +                       obj->page = p;
> > +               }
> > +       }
> 
> maybe leave out the small size case until there is a better way to
> deal with cache, since this seems like just an optimization..
> 
> although I wonder why not just use CMA?  (Other than maybe
> performance.. but I guess this is only for allocating scanout buffers,
> in which case all the bazillion of temporary pixmaps that X11
> creates/deletes won't be hitting this path..)

Those won't hit this path anyway.  Anyone with a shred of sanity wouldn't
create something like that for such a hotpath.  Calling into the kernel
isn't cheap, certainly calling into the kernel memory allocators isn't.
Maybe this is telling:

root at cubox:~# uptime
 17:32:35 up 4 days,  6:16,  2 users,  load average: 0.00, 0.01, 0.05
root at cubox:~# cat /sys/kernel/debug/dri/0/clients
a dev   pid    uid      magic     ioctls

y   0  1180     0          0     102829
root at cubox:~# ps aux | grep 1180
root      1180  0.1  3.2  39916 23564 tty7     Ss+  Jun06   6:43 /usr/bin/X :0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch

I cache the pixmaps in userspace for up to three seconds, which means
X's temporary pixmaps are not constantly allocated and freed... much like
the Intel i915 driver does.

Last point here - this path is _never_ used for X pixmaps.  The only
things which it's used for are the DUMB gem objects, so the scanout
buffer and the cursor pixmap.  So yes, the above could be removed,
which would place the cursor pixmap into the linear memory.

Why not CMA?  Apart from my violent distaste of that thing, and that it
ignores all the issues I brought up about multiple different mappings...
I refuse to use CMA.

> > +int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
> > +       struct drm_file *file)
> > +{
> > +       struct drm_armada_gem_prop *arg = data;
> > +       struct armada_gem_object *dobj;
> > +       int ret;
> > +
> > +       ret = mutex_lock_interruptible(&dev->struct_mutex);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dobj = armada_gem_object_lookup(dev, file, arg->handle);
> > +       if (dobj == NULL) {
> > +               ret = -ENOENT;
> > +               goto unlock;
> > +       }
> > +
> > +       arg->phys = dobj->phys_addr;
> 
> ugg, do you really need to expose phys addr to userspace?  Any chance
> to just teach the gpu bits about dmabuf instead?

Thankfully its usage is limited - and no, I'm not going to investigate
yet another chunk of massive DRM code called dmabuf.  The above is
needed to provide the physical address of the GEM buffers to the Vivante
GPU libraries and back into the kernel galcore driver.  Especially
important for proper fencing.

> > +struct drm_armada_gem_create {
> > +       uint32_t height;
> > +       uint32_t width;
> > +       uint32_t bpp;
> 
> just fwiw, typically height/width/bpp are properties of the fb but not
> the bo.. (except in some cases where kernel needs to know this to
> setup GTT correctly for tiled buffers)

This was there originally because it is required that these objects be
a certain pitch.  I've since moved that logic into the userspace buffer
manager, so the above three aren't used in my latest userspace.  I didn't
want to change the API yet again before all the RFC's were done, but
this will be gone.

> > +       uint32_t handle;
> > +       uint32_t pitch;
> > +       uint32_t size;
> > +};
> > +#define DRM_IOCTL_ARMADA_GEM_CREATE \
> > +       ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
> > +
> > +struct drm_armada_gem_create_phys {
> > +       uint32_t size;
> > +       uint32_t handle;
> > +       uint64_t phys;
> > +};
> > +#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
> > +       ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
> > +
> > +struct drm_armada_gem_mmap {
> > +       uint32_t handle;
> > +       uint32_t pad;
> > +       uint64_t offset;
> > +       uint64_t size;
> > +       uint64_t addr;
> > +};
> > +#define DRM_IOCTL_ARMADA_GEM_MMAP \
> > +       ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
> > +
> > +struct drm_armada_gem_pwrite {
> > +       uint32_t handle;
> > +       uint32_t offset;
> > +       uint32_t size;
> 
> probably want a uint32_t padding here, or move the uint64_t up in the
> struct to avoid 32 vs 64b alignment differences.

Yes.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 17:06       ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 17:06 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jason Cooper, David Airlie, dri-devel, Darren Etheridge,
	linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> <rmk+kernel@arm.linux.org.uk> wrote:
> > This patch adds support for the pair of LCD controllers on the Marvell
> > Armada 510 SoCs.  This driver supports:
> > - multiple contiguous scanout buffers for video and graphics
> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> >   acceleration
> > - dual lcd0 and lcd1 crt operation
> > - video overlay on each LCD crt
> 
> Any particular reason for not exposing the overlays as drm_plane's?
> That is probably something that should change before merging the
> driver.

Only through not understanding much of DRM when I started this.
Provided DRM planes can do everything that I'm already doing with
the overlay support, then yes.  Otherwise, I want to stick with this
which is modelled after the i915 overlay interface.

The big question that this brings up is that the plane stuff looks
a lot more heavyweight in terms of the computation done.  I'm already
concerned that I'm doing too much with the overlay code - it occasionally
misses getting the next frame of video ready before the VBLANK event
in certain circumstances which I haven't been able to quantify.  I'd
rather avoid adding too much additional work here, which would then
make overlay pretty useless for what it's meant for - video playback.

... and it looks to me like it can't - because I sometimes get blocks
of physical memory with the YUV data already in appropriately formatted
for scanout that I really would like to avoid having to expensively
copy.

> > - page flipping of the main scanout buffers
> >
> > Included in this commit is the core driver with no output support; output
> > support is platform and encoder driver dependent.
> >
> > Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> > ---
> 
> [snip]
> 
> > diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> > new file mode 100644
> > index 0000000..e5ab4cb
> > --- /dev/null
> > +++ b/drivers/gpu/drm/armada/armada_crtc.c
> > @@ -0,0 +1,766 @@
> > +/*
> > + * Copyright (C) 2012 Russell King
> > + *  Rewritten from the dovefb driver, and Armada510 manuals.
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +#include <linux/clk.h>
> > +#include <drm/drmP.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include "armada_crtc.h"
> > +#include "armada_drm.h"
> > +#include "armada_fb.h"
> > +#include "armada_gem.h"
> > +#include "armada_hw.h"
> > +
> > +struct armada_frame_work {
> > +       struct drm_pending_vblank_event *event;
> > +       struct armada_regs regs[4];
> > +       struct drm_gem_object *old_fb_obj;
> > +       struct work_struct work;
> > +};
> > +
> > +/*
> > + * A note about interlacing.  Let's consider HDMI 1920x1080i.
> > + * The timing parameters we have from X are:
> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> > + *  1920 2448 2492 2640  1080 1084 1094 1125
> > + * Which get translated to:
> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
> > + *  1920 2448 2492 2640   540  542  547  562
> > + *
> > + * This is how it is defined by CEA-861-D - line and pixel numbers are
> > + * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
> > + * line: 2640.  The odd frame, the first active line is at line 21, and
> > + * the even frame, the first active line is 584.
> > + *
> > + * LN:    560     561     562     563             567     568    569
> > + * DE:    ~~~|____________________________//__________________________
> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> > + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
> > + *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
> > + *
> > + * LN:    1123   1124    1125      1               5       6      7
> > + * DE:    ~~~|____________________________//__________________________
> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
> > + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
> > + *  23 blanking lines
> > + *
> > + * The Armada LCD Controller line and pixel numbers are, like X timings,
> > + * referenced to the top left of the active frame.
> > + *
> > + * So, translating these to our LCD controller:
> > + *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
> > + *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
> > + * Note: Vsync front porch remains constant!
> > + *
> > + * if (odd_frame) {
> > + *   vtotal = mode->crtc_vtotal + 1;
> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
> > + *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
> > + * } else {
> > + *   vtotal = mode->crtc_vtotal;
> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
> > + *   vhorizpos = mode->crtc_hsync_start;
> > + * }
> > + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
> > + *
> > + * So, we need to reprogram these registers on each vsync event:
> > + *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
> 
> ouch, proof that some hw designers must really hate driver writers ;-)

Yep - and interlace support is not something that can be done in a stable
way with the way the Linux kernel deals with interrupts (iow, with no
priority and strictly single-threaded.)  The problem is to do this
properly, you need the LCD interrupt able to interrupt any other interrupt
handler so you can reprogram the LCD controller within a very tight
timeframe (less than one display field.)

It works, but occasionally you will get a hiccup (mainly that's the
fault of the USB stack taking 2.25ms over some of its keyboard/mouse
interrupts!)

> > +/* The mode_config.mutex will be held for this call */
> > +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> > +       struct drm_framebuffer *old_fb)
> > +{
> > +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> > +       struct armada_regs regs[4];
> > +       unsigned i;
> > +
> > +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
> > +       armada_reg_queue_end(regs, i);
> > +
> > +       /* Wait for pending flips to complete */
> > +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
> > +
> > +       /* Take a reference to the new fb as we're using it */
> > +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
> 
> note that you probably want to ref/unref the fb (and let the fb hold a
> ref to the gem bo).. that will make life easier for planar formats too
> (as the fb should hold ref's to the bo for each plane)

I'll look into it, but it will take some time as the refcounting is far
from trivial or obvious in DRM...

>From what I can see there's no intrinsic refcounting between the fb
and the gem bo.

> > diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
> [snip]
> > +
> > +static struct drm_ioctl_desc armada_ioctls[] = {
> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
> 
> you might want just a single ioctl for creating gem objects, with a
> 'scanout' flag..  perhaps some future version of the hw doesn't need
> phys contig for scanout, and this would probably be easier to handle
> with a single ioctl that has an 'if (flags & SCANOUT &&
> device_needs_phys_contig_scanout()) .. '

The purposes of these two ioctls are quite different.  ARMADA_GEM_CREATE
is to create any gem buffer object that needs shmem backing - eg,
non-scanout pixmaps and the like.

ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
This allows my X server to remain compatible with the XF86 Dove driver,
which permits the passing of the physical address of the video frame to
the X server (otherwise we're into rewriting a whole load more code than
is really desirable - and I really don't have time to rewrite bits of
gstreamer and other stuff.)

Lastly, the DUMB stuff is used for the graphics scanout buffer.

> > diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
> [snip]
> > +int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
> > +{
> > +       struct armada_private *priv = dev->dev_private;
> > +       size_t size = obj->obj.size;
> > +
> > +       if (obj->page || obj->linear)
> > +               return 0;
> > +
> > +       /* If it is a small allocation, try to get it from the system */
> > +       if (size < 1048576) {
> > +               unsigned int order = get_order(size);
> > +               struct page *p = alloc_pages(GFP_KERNEL, order);
> > +
> > +               if (p) {
> > +                       unsigned sz = (size + 31) & ~31;
> > +                       uintptr_t ptr;
> > +
> > +                       obj->phys_addr = page_to_phys(p);
> > +                       obj->dev_addr = obj->phys_addr;
> > +
> > +                       /* FIXME: Hack around dirty cache */
> > +                       ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
> > +                       memset((void *)ptr, 0, PAGE_ALIGN(size));
> > +                       while (sz) {
> > +                               asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
> > +                               ptr += 32;
> > +                               sz -= 32;
> > +                       }
> > +                       dsb();
> > +
> > +                       obj->page = p;
> > +               }
> > +       }
> 
> maybe leave out the small size case until there is a better way to
> deal with cache, since this seems like just an optimization..
> 
> although I wonder why not just use CMA?  (Other than maybe
> performance.. but I guess this is only for allocating scanout buffers,
> in which case all the bazillion of temporary pixmaps that X11
> creates/deletes won't be hitting this path..)

Those won't hit this path anyway.  Anyone with a shred of sanity wouldn't
create something like that for such a hotpath.  Calling into the kernel
isn't cheap, certainly calling into the kernel memory allocators isn't.
Maybe this is telling:

root@cubox:~# uptime
 17:32:35 up 4 days,  6:16,  2 users,  load average: 0.00, 0.01, 0.05
root@cubox:~# cat /sys/kernel/debug/dri/0/clients
a dev   pid    uid      magic     ioctls

y   0  1180     0          0     102829
root@cubox:~# ps aux | grep 1180
root      1180  0.1  3.2  39916 23564 tty7     Ss+  Jun06   6:43 /usr/bin/X :0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch

I cache the pixmaps in userspace for up to three seconds, which means
X's temporary pixmaps are not constantly allocated and freed... much like
the Intel i915 driver does.

Last point here - this path is _never_ used for X pixmaps.  The only
things which it's used for are the DUMB gem objects, so the scanout
buffer and the cursor pixmap.  So yes, the above could be removed,
which would place the cursor pixmap into the linear memory.

Why not CMA?  Apart from my violent distaste of that thing, and that it
ignores all the issues I brought up about multiple different mappings...
I refuse to use CMA.

> > +int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
> > +       struct drm_file *file)
> > +{
> > +       struct drm_armada_gem_prop *arg = data;
> > +       struct armada_gem_object *dobj;
> > +       int ret;
> > +
> > +       ret = mutex_lock_interruptible(&dev->struct_mutex);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dobj = armada_gem_object_lookup(dev, file, arg->handle);
> > +       if (dobj == NULL) {
> > +               ret = -ENOENT;
> > +               goto unlock;
> > +       }
> > +
> > +       arg->phys = dobj->phys_addr;
> 
> ugg, do you really need to expose phys addr to userspace?  Any chance
> to just teach the gpu bits about dmabuf instead?

Thankfully its usage is limited - and no, I'm not going to investigate
yet another chunk of massive DRM code called dmabuf.  The above is
needed to provide the physical address of the GEM buffers to the Vivante
GPU libraries and back into the kernel galcore driver.  Especially
important for proper fencing.

> > +struct drm_armada_gem_create {
> > +       uint32_t height;
> > +       uint32_t width;
> > +       uint32_t bpp;
> 
> just fwiw, typically height/width/bpp are properties of the fb but not
> the bo.. (except in some cases where kernel needs to know this to
> setup GTT correctly for tiled buffers)

This was there originally because it is required that these objects be
a certain pitch.  I've since moved that logic into the userspace buffer
manager, so the above three aren't used in my latest userspace.  I didn't
want to change the API yet again before all the RFC's were done, but
this will be gone.

> > +       uint32_t handle;
> > +       uint32_t pitch;
> > +       uint32_t size;
> > +};
> > +#define DRM_IOCTL_ARMADA_GEM_CREATE \
> > +       ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
> > +
> > +struct drm_armada_gem_create_phys {
> > +       uint32_t size;
> > +       uint32_t handle;
> > +       uint64_t phys;
> > +};
> > +#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
> > +       ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
> > +
> > +struct drm_armada_gem_mmap {
> > +       uint32_t handle;
> > +       uint32_t pad;
> > +       uint64_t offset;
> > +       uint64_t size;
> > +       uint64_t addr;
> > +};
> > +#define DRM_IOCTL_ARMADA_GEM_MMAP \
> > +       ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
> > +
> > +struct drm_armada_gem_pwrite {
> > +       uint32_t handle;
> > +       uint32_t offset;
> > +       uint32_t size;
> 
> probably want a uint32_t padding here, or move the uint64_t up in the
> struct to avoid 32 vs 64b alignment differences.

Yes.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 17:06       ` Russell King - ARM Linux
@ 2013-06-10 19:59         ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 19:59 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
>> <rmk+kernel@arm.linux.org.uk> wrote:
>> > This patch adds support for the pair of LCD controllers on the Marvell
>> > Armada 510 SoCs.  This driver supports:
>> > - multiple contiguous scanout buffers for video and graphics
>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>> >   acceleration
>> > - dual lcd0 and lcd1 crt operation
>> > - video overlay on each LCD crt
>>
>> Any particular reason for not exposing the overlays as drm_plane's?
>> That is probably something that should change before merging the
>> driver.
>
> Only through not understanding much of DRM when I started this.
> Provided DRM planes can do everything that I'm already doing with
> the overlay support, then yes.  Otherwise, I want to stick with this
> which is modelled after the i915 overlay interface.
>
> The big question that this brings up is that the plane stuff looks
> a lot more heavyweight in terms of the computation done.  I'm already
> concerned that I'm doing too much with the overlay code - it occasionally
> misses getting the next frame of video ready before the VBLANK event
> in certain circumstances which I haven't been able to quantify.  I'd
> rather avoid adding too much additional work here, which would then
> make overlay pretty useless for what it's meant for - video playback.
>
> ... and it looks to me like it can't - because I sometimes get blocks
> of physical memory with the YUV data already in appropriately formatted
> for scanout that I really would like to avoid having to expensively
> copy.

I guess I'm not entirely sure that I understand your concern here..
all that planes require is a drm fb to scan out.  The GEM bo(s) that
make up the backing store for that fb can be anything (phys contig if
you need, etc).  You could keep your putimage ioctl to dma into an fb.
 Or if the decoder can decode directly into something that could be
scanned out, you can do that too.

I don't think anything about drm plane is computationally expensive.
It should be about on par w/ pageflip ioctl (ie. handful of error
checks, and then call into the driver).

>> > - page flipping of the main scanout buffers
>> >
>> > Included in this commit is the core driver with no output support; output
>> > support is platform and encoder driver dependent.
>> >
>> > Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
>> > ---
>>
>> [snip]
>>
>> > diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
>> > new file mode 100644
>> > index 0000000..e5ab4cb
>> > --- /dev/null
>> > +++ b/drivers/gpu/drm/armada/armada_crtc.c
>> > @@ -0,0 +1,766 @@
>> > +/*
>> > + * Copyright (C) 2012 Russell King
>> > + *  Rewritten from the dovefb driver, and Armada510 manuals.
>> > + *
>> > + * This program is free software; you can redistribute it and/or modify
>> > + * it under the terms of the GNU General Public License version 2 as
>> > + * published by the Free Software Foundation.
>> > + */
>> > +#include <linux/clk.h>
>> > +#include <drm/drmP.h>
>> > +#include <drm/drm_crtc_helper.h>
>> > +#include "armada_crtc.h"
>> > +#include "armada_drm.h"
>> > +#include "armada_fb.h"
>> > +#include "armada_gem.h"
>> > +#include "armada_hw.h"
>> > +
>> > +struct armada_frame_work {
>> > +       struct drm_pending_vblank_event *event;
>> > +       struct armada_regs regs[4];
>> > +       struct drm_gem_object *old_fb_obj;
>> > +       struct work_struct work;
>> > +};
>> > +
>> > +/*
>> > + * A note about interlacing.  Let's consider HDMI 1920x1080i.
>> > + * The timing parameters we have from X are:
>> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
>> > + *  1920 2448 2492 2640  1080 1084 1094 1125
>> > + * Which get translated to:
>> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
>> > + *  1920 2448 2492 2640   540  542  547  562
>> > + *
>> > + * This is how it is defined by CEA-861-D - line and pixel numbers are
>> > + * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
>> > + * line: 2640.  The odd frame, the first active line is at line 21, and
>> > + * the even frame, the first active line is 584.
>> > + *
>> > + * LN:    560     561     562     563             567     568    569
>> > + * DE:    ~~~|____________________________//__________________________
>> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
>> > + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
>> > + *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
>> > + *
>> > + * LN:    1123   1124    1125      1               5       6      7
>> > + * DE:    ~~~|____________________________//__________________________
>> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
>> > + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
>> > + *  23 blanking lines
>> > + *
>> > + * The Armada LCD Controller line and pixel numbers are, like X timings,
>> > + * referenced to the top left of the active frame.
>> > + *
>> > + * So, translating these to our LCD controller:
>> > + *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
>> > + *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
>> > + * Note: Vsync front porch remains constant!
>> > + *
>> > + * if (odd_frame) {
>> > + *   vtotal = mode->crtc_vtotal + 1;
>> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
>> > + *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
>> > + * } else {
>> > + *   vtotal = mode->crtc_vtotal;
>> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
>> > + *   vhorizpos = mode->crtc_hsync_start;
>> > + * }
>> > + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
>> > + *
>> > + * So, we need to reprogram these registers on each vsync event:
>> > + *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
>>
>> ouch, proof that some hw designers must really hate driver writers ;-)
>
> Yep - and interlace support is not something that can be done in a stable
> way with the way the Linux kernel deals with interrupts (iow, with no
> priority and strictly single-threaded.)  The problem is to do this
> properly, you need the LCD interrupt able to interrupt any other interrupt
> handler so you can reprogram the LCD controller within a very tight
> timeframe (less than one display field.)
>
> It works, but occasionally you will get a hiccup (mainly that's the
> fault of the USB stack taking 2.25ms over some of its keyboard/mouse
> interrupts!)

sounds like a job for fiq

>> > +/* The mode_config.mutex will be held for this call */
>> > +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>> > +       struct drm_framebuffer *old_fb)
>> > +{
>> > +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
>> > +       struct armada_regs regs[4];
>> > +       unsigned i;
>> > +
>> > +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
>> > +       armada_reg_queue_end(regs, i);
>> > +
>> > +       /* Wait for pending flips to complete */
>> > +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
>> > +
>> > +       /* Take a reference to the new fb as we're using it */
>> > +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
>>
>> note that you probably want to ref/unref the fb (and let the fb hold a
>> ref to the gem bo).. that will make life easier for planar formats too
>> (as the fb should hold ref's to the bo for each plane)
>
> I'll look into it, but it will take some time as the refcounting is far
> from trivial or obvious in DRM...
>
> From what I can see there's no intrinsic refcounting between the fb
> and the gem bo.
>
>> > diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
>> [snip]
>> > +
>> > +static struct drm_ioctl_desc armada_ioctls[] = {
>> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
>> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
>>
>> you might want just a single ioctl for creating gem objects, with a
>> 'scanout' flag..  perhaps some future version of the hw doesn't need
>> phys contig for scanout, and this would probably be easier to handle
>> with a single ioctl that has an 'if (flags & SCANOUT &&
>> device_needs_phys_contig_scanout()) .. '
>
> The purposes of these two ioctls are quite different.  ARMADA_GEM_CREATE
> is to create any gem buffer object that needs shmem backing - eg,
> non-scanout pixmaps and the like.
>
> ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
> a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
> This allows my X server to remain compatible with the XF86 Dove driver,
> which permits the passing of the physical address of the video frame to
> the X server (otherwise we're into rewriting a whole load more code than
> is really desirable - and I really don't have time to rewrite bits of
> gstreamer and other stuff.)

ahh, gotcha.. and, ugg, that is even worse..

I'm not hugely a fan of giving userspace a way to dma into arbitrary
phys addresses (ie. create_phys + put_image).  But I don't need to
explain what you already know ;-)

Is there a pre-defined carveout pool that you can at least bounds
check against?  Otherwise put this ioctl inside a #if
CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH / #endif..


> Lastly, the DUMB stuff is used for the graphics scanout buffer.
>
>> > diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
>> [snip]
>> > +int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
>> > +{
>> > +       struct armada_private *priv = dev->dev_private;
>> > +       size_t size = obj->obj.size;
>> > +
>> > +       if (obj->page || obj->linear)
>> > +               return 0;
>> > +
>> > +       /* If it is a small allocation, try to get it from the system */
>> > +       if (size < 1048576) {
>> > +               unsigned int order = get_order(size);
>> > +               struct page *p = alloc_pages(GFP_KERNEL, order);
>> > +
>> > +               if (p) {
>> > +                       unsigned sz = (size + 31) & ~31;
>> > +                       uintptr_t ptr;
>> > +
>> > +                       obj->phys_addr = page_to_phys(p);
>> > +                       obj->dev_addr = obj->phys_addr;
>> > +
>> > +                       /* FIXME: Hack around dirty cache */
>> > +                       ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
>> > +                       memset((void *)ptr, 0, PAGE_ALIGN(size));
>> > +                       while (sz) {
>> > +                               asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
>> > +                               ptr += 32;
>> > +                               sz -= 32;
>> > +                       }
>> > +                       dsb();
>> > +
>> > +                       obj->page = p;
>> > +               }
>> > +       }
>>
>> maybe leave out the small size case until there is a better way to
>> deal with cache, since this seems like just an optimization..
>>
>> although I wonder why not just use CMA?  (Other than maybe
>> performance.. but I guess this is only for allocating scanout buffers,
>> in which case all the bazillion of temporary pixmaps that X11
>> creates/deletes won't be hitting this path..)
>
> Those won't hit this path anyway.  Anyone with a shred of sanity wouldn't
> create something like that for such a hotpath.  Calling into the kernel
> isn't cheap, certainly calling into the kernel memory allocators isn't.
> Maybe this is telling:
>
> root at cubox:~# uptime
>  17:32:35 up 4 days,  6:16,  2 users,  load average: 0.00, 0.01, 0.05
> root at cubox:~# cat /sys/kernel/debug/dri/0/clients
> a dev   pid    uid      magic     ioctls
>
> y   0  1180     0          0     102829
> root at cubox:~# ps aux | grep 1180
> root      1180  0.1  3.2  39916 23564 tty7     Ss+  Jun06   6:43 /usr/bin/X :0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
>
> I cache the pixmaps in userspace for up to three seconds, which means
> X's temporary pixmaps are not constantly allocated and freed... much like
> the Intel i915 driver does.
>
> Last point here - this path is _never_ used for X pixmaps.  The only
> things which it's used for are the DUMB gem objects, so the scanout
> buffer and the cursor pixmap.  So yes, the above could be removed,
> which would place the cursor pixmap into the linear memory.
>
> Why not CMA?  Apart from my violent distaste of that thing, and that it
> ignores all the issues I brought up about multiple different mappings...
> I refuse to use CMA.
>
>> > +int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
>> > +       struct drm_file *file)
>> > +{
>> > +       struct drm_armada_gem_prop *arg = data;
>> > +       struct armada_gem_object *dobj;
>> > +       int ret;
>> > +
>> > +       ret = mutex_lock_interruptible(&dev->struct_mutex);
>> > +       if (ret)
>> > +               return ret;
>> > +
>> > +       dobj = armada_gem_object_lookup(dev, file, arg->handle);
>> > +       if (dobj == NULL) {
>> > +               ret = -ENOENT;
>> > +               goto unlock;
>> > +       }
>> > +
>> > +       arg->phys = dobj->phys_addr;
>>
>> ugg, do you really need to expose phys addr to userspace?  Any chance
>> to just teach the gpu bits about dmabuf instead?
>
> Thankfully its usage is limited - and no, I'm not going to investigate
> yet another chunk of massive DRM code called dmabuf.  The above is
> needed to provide the physical address of the GEM buffers to the Vivante
> GPU libraries and back into the kernel galcore driver.  Especially
> important for proper fencing.

hmm, well dmabuf isn't too bad (drm core takes care of a lot of it for
you)..  I don't know how much the impact would be to add dmabuf
support in the various other drivers that you have to care about.
Maybe '#if CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH' it..
or at least keep it a patch on top of the upstream driver since it is
the sort of thing that you'd want to get rid of if the other marvel
drivers eventually give you a way to do dmabuf?

I'm not really sure what others think, but I am uneasy about this and
the create_phys ioctl.

>> > +struct drm_armada_gem_create {
>> > +       uint32_t height;
>> > +       uint32_t width;
>> > +       uint32_t bpp;
>>
>> just fwiw, typically height/width/bpp are properties of the fb but not
>> the bo.. (except in some cases where kernel needs to know this to
>> setup GTT correctly for tiled buffers)
>
> This was there originally because it is required that these objects be
> a certain pitch.  I've since moved that logic into the userspace buffer
> manager, so the above three aren't used in my latest userspace.  I didn't
> want to change the API yet again before all the RFC's were done, but
> this will be gone.

sure, no prob.  Probably worth mentioning in the commit msg, though ;-)


BR,
-R


>> > +       uint32_t handle;
>> > +       uint32_t pitch;
>> > +       uint32_t size;
>> > +};
>> > +#define DRM_IOCTL_ARMADA_GEM_CREATE \
>> > +       ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
>> > +
>> > +struct drm_armada_gem_create_phys {
>> > +       uint32_t size;
>> > +       uint32_t handle;
>> > +       uint64_t phys;
>> > +};
>> > +#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
>> > +       ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
>> > +
>> > +struct drm_armada_gem_mmap {
>> > +       uint32_t handle;
>> > +       uint32_t pad;
>> > +       uint64_t offset;
>> > +       uint64_t size;
>> > +       uint64_t addr;
>> > +};
>> > +#define DRM_IOCTL_ARMADA_GEM_MMAP \
>> > +       ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
>> > +
>> > +struct drm_armada_gem_pwrite {
>> > +       uint32_t handle;
>> > +       uint32_t offset;
>> > +       uint32_t size;
>>
>> probably want a uint32_t padding here, or move the uint64_t up in the
>> struct to avoid 32 vs 64b alignment differences.
>
> Yes.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 19:59         ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 19:59 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
>> <rmk+kernel@arm.linux.org.uk> wrote:
>> > This patch adds support for the pair of LCD controllers on the Marvell
>> > Armada 510 SoCs.  This driver supports:
>> > - multiple contiguous scanout buffers for video and graphics
>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>> >   acceleration
>> > - dual lcd0 and lcd1 crt operation
>> > - video overlay on each LCD crt
>>
>> Any particular reason for not exposing the overlays as drm_plane's?
>> That is probably something that should change before merging the
>> driver.
>
> Only through not understanding much of DRM when I started this.
> Provided DRM planes can do everything that I'm already doing with
> the overlay support, then yes.  Otherwise, I want to stick with this
> which is modelled after the i915 overlay interface.
>
> The big question that this brings up is that the plane stuff looks
> a lot more heavyweight in terms of the computation done.  I'm already
> concerned that I'm doing too much with the overlay code - it occasionally
> misses getting the next frame of video ready before the VBLANK event
> in certain circumstances which I haven't been able to quantify.  I'd
> rather avoid adding too much additional work here, which would then
> make overlay pretty useless for what it's meant for - video playback.
>
> ... and it looks to me like it can't - because I sometimes get blocks
> of physical memory with the YUV data already in appropriately formatted
> for scanout that I really would like to avoid having to expensively
> copy.

I guess I'm not entirely sure that I understand your concern here..
all that planes require is a drm fb to scan out.  The GEM bo(s) that
make up the backing store for that fb can be anything (phys contig if
you need, etc).  You could keep your putimage ioctl to dma into an fb.
 Or if the decoder can decode directly into something that could be
scanned out, you can do that too.

I don't think anything about drm plane is computationally expensive.
It should be about on par w/ pageflip ioctl (ie. handful of error
checks, and then call into the driver).

>> > - page flipping of the main scanout buffers
>> >
>> > Included in this commit is the core driver with no output support; output
>> > support is platform and encoder driver dependent.
>> >
>> > Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
>> > ---
>>
>> [snip]
>>
>> > diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
>> > new file mode 100644
>> > index 0000000..e5ab4cb
>> > --- /dev/null
>> > +++ b/drivers/gpu/drm/armada/armada_crtc.c
>> > @@ -0,0 +1,766 @@
>> > +/*
>> > + * Copyright (C) 2012 Russell King
>> > + *  Rewritten from the dovefb driver, and Armada510 manuals.
>> > + *
>> > + * This program is free software; you can redistribute it and/or modify
>> > + * it under the terms of the GNU General Public License version 2 as
>> > + * published by the Free Software Foundation.
>> > + */
>> > +#include <linux/clk.h>
>> > +#include <drm/drmP.h>
>> > +#include <drm/drm_crtc_helper.h>
>> > +#include "armada_crtc.h"
>> > +#include "armada_drm.h"
>> > +#include "armada_fb.h"
>> > +#include "armada_gem.h"
>> > +#include "armada_hw.h"
>> > +
>> > +struct armada_frame_work {
>> > +       struct drm_pending_vblank_event *event;
>> > +       struct armada_regs regs[4];
>> > +       struct drm_gem_object *old_fb_obj;
>> > +       struct work_struct work;
>> > +};
>> > +
>> > +/*
>> > + * A note about interlacing.  Let's consider HDMI 1920x1080i.
>> > + * The timing parameters we have from X are:
>> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
>> > + *  1920 2448 2492 2640  1080 1084 1094 1125
>> > + * Which get translated to:
>> > + *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
>> > + *  1920 2448 2492 2640   540  542  547  562
>> > + *
>> > + * This is how it is defined by CEA-861-D - line and pixel numbers are
>> > + * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
>> > + * line: 2640.  The odd frame, the first active line is at line 21, and
>> > + * the even frame, the first active line is 584.
>> > + *
>> > + * LN:    560     561     562     563             567     568    569
>> > + * DE:    ~~~|____________________________//__________________________
>> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
>> > + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
>> > + *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
>> > + *
>> > + * LN:    1123   1124    1125      1               5       6      7
>> > + * DE:    ~~~|____________________________//__________________________
>> > + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
>> > + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
>> > + *  23 blanking lines
>> > + *
>> > + * The Armada LCD Controller line and pixel numbers are, like X timings,
>> > + * referenced to the top left of the active frame.
>> > + *
>> > + * So, translating these to our LCD controller:
>> > + *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
>> > + *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
>> > + * Note: Vsync front porch remains constant!
>> > + *
>> > + * if (odd_frame) {
>> > + *   vtotal = mode->crtc_vtotal + 1;
>> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
>> > + *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
>> > + * } else {
>> > + *   vtotal = mode->crtc_vtotal;
>> > + *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
>> > + *   vhorizpos = mode->crtc_hsync_start;
>> > + * }
>> > + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
>> > + *
>> > + * So, we need to reprogram these registers on each vsync event:
>> > + *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
>>
>> ouch, proof that some hw designers must really hate driver writers ;-)
>
> Yep - and interlace support is not something that can be done in a stable
> way with the way the Linux kernel deals with interrupts (iow, with no
> priority and strictly single-threaded.)  The problem is to do this
> properly, you need the LCD interrupt able to interrupt any other interrupt
> handler so you can reprogram the LCD controller within a very tight
> timeframe (less than one display field.)
>
> It works, but occasionally you will get a hiccup (mainly that's the
> fault of the USB stack taking 2.25ms over some of its keyboard/mouse
> interrupts!)

sounds like a job for fiq

>> > +/* The mode_config.mutex will be held for this call */
>> > +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>> > +       struct drm_framebuffer *old_fb)
>> > +{
>> > +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
>> > +       struct armada_regs regs[4];
>> > +       unsigned i;
>> > +
>> > +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
>> > +       armada_reg_queue_end(regs, i);
>> > +
>> > +       /* Wait for pending flips to complete */
>> > +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
>> > +
>> > +       /* Take a reference to the new fb as we're using it */
>> > +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
>>
>> note that you probably want to ref/unref the fb (and let the fb hold a
>> ref to the gem bo).. that will make life easier for planar formats too
>> (as the fb should hold ref's to the bo for each plane)
>
> I'll look into it, but it will take some time as the refcounting is far
> from trivial or obvious in DRM...
>
> From what I can see there's no intrinsic refcounting between the fb
> and the gem bo.
>
>> > diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
>> [snip]
>> > +
>> > +static struct drm_ioctl_desc armada_ioctls[] = {
>> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
>> > +       DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
>>
>> you might want just a single ioctl for creating gem objects, with a
>> 'scanout' flag..  perhaps some future version of the hw doesn't need
>> phys contig for scanout, and this would probably be easier to handle
>> with a single ioctl that has an 'if (flags & SCANOUT &&
>> device_needs_phys_contig_scanout()) .. '
>
> The purposes of these two ioctls are quite different.  ARMADA_GEM_CREATE
> is to create any gem buffer object that needs shmem backing - eg,
> non-scanout pixmaps and the like.
>
> ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
> a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
> This allows my X server to remain compatible with the XF86 Dove driver,
> which permits the passing of the physical address of the video frame to
> the X server (otherwise we're into rewriting a whole load more code than
> is really desirable - and I really don't have time to rewrite bits of
> gstreamer and other stuff.)

ahh, gotcha.. and, ugg, that is even worse..

I'm not hugely a fan of giving userspace a way to dma into arbitrary
phys addresses (ie. create_phys + put_image).  But I don't need to
explain what you already know ;-)

Is there a pre-defined carveout pool that you can at least bounds
check against?  Otherwise put this ioctl inside a #if
CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH / #endif..


> Lastly, the DUMB stuff is used for the graphics scanout buffer.
>
>> > diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
>> [snip]
>> > +int armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
>> > +{
>> > +       struct armada_private *priv = dev->dev_private;
>> > +       size_t size = obj->obj.size;
>> > +
>> > +       if (obj->page || obj->linear)
>> > +               return 0;
>> > +
>> > +       /* If it is a small allocation, try to get it from the system */
>> > +       if (size < 1048576) {
>> > +               unsigned int order = get_order(size);
>> > +               struct page *p = alloc_pages(GFP_KERNEL, order);
>> > +
>> > +               if (p) {
>> > +                       unsigned sz = (size + 31) & ~31;
>> > +                       uintptr_t ptr;
>> > +
>> > +                       obj->phys_addr = page_to_phys(p);
>> > +                       obj->dev_addr = obj->phys_addr;
>> > +
>> > +                       /* FIXME: Hack around dirty cache */
>> > +                       ptr = (uintptr_t)phys_to_virt(obj->phys_addr);
>> > +                       memset((void *)ptr, 0, PAGE_ALIGN(size));
>> > +                       while (sz) {
>> > +                               asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
>> > +                               ptr += 32;
>> > +                               sz -= 32;
>> > +                       }
>> > +                       dsb();
>> > +
>> > +                       obj->page = p;
>> > +               }
>> > +       }
>>
>> maybe leave out the small size case until there is a better way to
>> deal with cache, since this seems like just an optimization..
>>
>> although I wonder why not just use CMA?  (Other than maybe
>> performance.. but I guess this is only for allocating scanout buffers,
>> in which case all the bazillion of temporary pixmaps that X11
>> creates/deletes won't be hitting this path..)
>
> Those won't hit this path anyway.  Anyone with a shred of sanity wouldn't
> create something like that for such a hotpath.  Calling into the kernel
> isn't cheap, certainly calling into the kernel memory allocators isn't.
> Maybe this is telling:
>
> root@cubox:~# uptime
>  17:32:35 up 4 days,  6:16,  2 users,  load average: 0.00, 0.01, 0.05
> root@cubox:~# cat /sys/kernel/debug/dri/0/clients
> a dev   pid    uid      magic     ioctls
>
> y   0  1180     0          0     102829
> root@cubox:~# ps aux | grep 1180
> root      1180  0.1  3.2  39916 23564 tty7     Ss+  Jun06   6:43 /usr/bin/X :0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
>
> I cache the pixmaps in userspace for up to three seconds, which means
> X's temporary pixmaps are not constantly allocated and freed... much like
> the Intel i915 driver does.
>
> Last point here - this path is _never_ used for X pixmaps.  The only
> things which it's used for are the DUMB gem objects, so the scanout
> buffer and the cursor pixmap.  So yes, the above could be removed,
> which would place the cursor pixmap into the linear memory.
>
> Why not CMA?  Apart from my violent distaste of that thing, and that it
> ignores all the issues I brought up about multiple different mappings...
> I refuse to use CMA.
>
>> > +int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
>> > +       struct drm_file *file)
>> > +{
>> > +       struct drm_armada_gem_prop *arg = data;
>> > +       struct armada_gem_object *dobj;
>> > +       int ret;
>> > +
>> > +       ret = mutex_lock_interruptible(&dev->struct_mutex);
>> > +       if (ret)
>> > +               return ret;
>> > +
>> > +       dobj = armada_gem_object_lookup(dev, file, arg->handle);
>> > +       if (dobj == NULL) {
>> > +               ret = -ENOENT;
>> > +               goto unlock;
>> > +       }
>> > +
>> > +       arg->phys = dobj->phys_addr;
>>
>> ugg, do you really need to expose phys addr to userspace?  Any chance
>> to just teach the gpu bits about dmabuf instead?
>
> Thankfully its usage is limited - and no, I'm not going to investigate
> yet another chunk of massive DRM code called dmabuf.  The above is
> needed to provide the physical address of the GEM buffers to the Vivante
> GPU libraries and back into the kernel galcore driver.  Especially
> important for proper fencing.

hmm, well dmabuf isn't too bad (drm core takes care of a lot of it for
you)..  I don't know how much the impact would be to add dmabuf
support in the various other drivers that you have to care about.
Maybe '#if CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH' it..
or at least keep it a patch on top of the upstream driver since it is
the sort of thing that you'd want to get rid of if the other marvel
drivers eventually give you a way to do dmabuf?

I'm not really sure what others think, but I am uneasy about this and
the create_phys ioctl.

>> > +struct drm_armada_gem_create {
>> > +       uint32_t height;
>> > +       uint32_t width;
>> > +       uint32_t bpp;
>>
>> just fwiw, typically height/width/bpp are properties of the fb but not
>> the bo.. (except in some cases where kernel needs to know this to
>> setup GTT correctly for tiled buffers)
>
> This was there originally because it is required that these objects be
> a certain pitch.  I've since moved that logic into the userspace buffer
> manager, so the above three aren't used in my latest userspace.  I didn't
> want to change the API yet again before all the RFC's were done, but
> this will be gone.

sure, no prob.  Probably worth mentioning in the commit msg, though ;-)


BR,
-R


>> > +       uint32_t handle;
>> > +       uint32_t pitch;
>> > +       uint32_t size;
>> > +};
>> > +#define DRM_IOCTL_ARMADA_GEM_CREATE \
>> > +       ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
>> > +
>> > +struct drm_armada_gem_create_phys {
>> > +       uint32_t size;
>> > +       uint32_t handle;
>> > +       uint64_t phys;
>> > +};
>> > +#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
>> > +       ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
>> > +
>> > +struct drm_armada_gem_mmap {
>> > +       uint32_t handle;
>> > +       uint32_t pad;
>> > +       uint64_t offset;
>> > +       uint64_t size;
>> > +       uint64_t addr;
>> > +};
>> > +#define DRM_IOCTL_ARMADA_GEM_MMAP \
>> > +       ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
>> > +
>> > +struct drm_armada_gem_pwrite {
>> > +       uint32_t handle;
>> > +       uint32_t offset;
>> > +       uint32_t size;
>>
>> probably want a uint32_t padding here, or move the uint64_t up in the
>> struct to avoid 32 vs 64b alignment differences.
>
> Yes.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 19:59         ` Rob Clark
@ 2013-06-10 20:08           ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 20:08 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 03:59:34PM -0400, Rob Clark wrote:
> On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
> > a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
> > This allows my X server to remain compatible with the XF86 Dove driver,
> > which permits the passing of the physical address of the video frame to
> > the X server (otherwise we're into rewriting a whole load more code than
> > is really desirable - and I really don't have time to rewrite bits of
> > gstreamer and other stuff.)
> 
> ahh, gotcha.. and, ugg, that is even worse..
> 
> I'm not hugely a fan of giving userspace a way to dma into arbitrary
> phys addresses (ie. create_phys + put_image).  But I don't need to
> explain what you already know ;-)
> 
> Is there a pre-defined carveout pool that you can at least bounds
> check against?  Otherwise put this ioctl inside a #if
> CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH / #endif..

This driver is _not_ about supporting the GPU natively as part of the DRM,
but providing the foundations for:

(a) a properly functional interface to HDMI TVs (fbdev doesn't hack that)
    with hotplug
(b) providing sufficient infrastructure to allow video playback to work.

What this is not about is fixing the crappy userspace GPU libraries and
video decoding so that it's "secure" - not only is that way beyond my
abilities, it would also be a violation of the closed source license to
reverse engineer them so that were possible.

It is something that continues to be a problem and I'm making no claims
that I'm fixing that.  So no, I will not put such a config option around
it for the simple reason that by doing so, turning such an option off
renders userspace utterly useless and the driver might as well not exist.

If that means you want to NACK the patch, then fine; I'll just maintain
it out of tree.  I did the driver mostly for my own personal benefit to
get the Cubox to the point where I can boot it with or without the TV
connected and have it work.  But it would be a shame if that meant
that others could not benefit from about 8 solid months of my work on
this and have to put up with crappy a fbdev driver.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 20:08           ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 20:08 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jason Cooper, David Airlie, dri-devel, Darren Etheridge,
	linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 03:59:34PM -0400, Rob Clark wrote:
> On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
> > a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
> > This allows my X server to remain compatible with the XF86 Dove driver,
> > which permits the passing of the physical address of the video frame to
> > the X server (otherwise we're into rewriting a whole load more code than
> > is really desirable - and I really don't have time to rewrite bits of
> > gstreamer and other stuff.)
> 
> ahh, gotcha.. and, ugg, that is even worse..
> 
> I'm not hugely a fan of giving userspace a way to dma into arbitrary
> phys addresses (ie. create_phys + put_image).  But I don't need to
> explain what you already know ;-)
> 
> Is there a pre-defined carveout pool that you can at least bounds
> check against?  Otherwise put this ioctl inside a #if
> CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH / #endif..

This driver is _not_ about supporting the GPU natively as part of the DRM,
but providing the foundations for:

(a) a properly functional interface to HDMI TVs (fbdev doesn't hack that)
    with hotplug
(b) providing sufficient infrastructure to allow video playback to work.

What this is not about is fixing the crappy userspace GPU libraries and
video decoding so that it's "secure" - not only is that way beyond my
abilities, it would also be a violation of the closed source license to
reverse engineer them so that were possible.

It is something that continues to be a problem and I'm making no claims
that I'm fixing that.  So no, I will not put such a config option around
it for the simple reason that by doing so, turning such an option off
renders userspace utterly useless and the driver might as well not exist.

If that means you want to NACK the patch, then fine; I'll just maintain
it out of tree.  I did the driver mostly for my own personal benefit to
get the Cubox to the point where I can boot it with or without the TV
connected and have it work.  But it would be a shame if that meant
that others could not benefit from about 8 solid months of my work on
this and have to put up with crappy a fbdev driver.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 20:08           ` Russell King - ARM Linux
@ 2013-06-10 21:01             ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 21:01 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 4:08 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 03:59:34PM -0400, Rob Clark wrote:
>> On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
>> > a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
>> > This allows my X server to remain compatible with the XF86 Dove driver,
>> > which permits the passing of the physical address of the video frame to
>> > the X server (otherwise we're into rewriting a whole load more code than
>> > is really desirable - and I really don't have time to rewrite bits of
>> > gstreamer and other stuff.)
>>
>> ahh, gotcha.. and, ugg, that is even worse..
>>
>> I'm not hugely a fan of giving userspace a way to dma into arbitrary
>> phys addresses (ie. create_phys + put_image).  But I don't need to
>> explain what you already know ;-)
>>
>> Is there a pre-defined carveout pool that you can at least bounds
>> check against?  Otherwise put this ioctl inside a #if
>> CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH / #endif..
>
> This driver is _not_ about supporting the GPU natively as part of the DRM,
> but providing the foundations for:
>
> (a) a properly functional interface to HDMI TVs (fbdev doesn't hack that)
>     with hotplug
> (b) providing sufficient infrastructure to allow video playback to work.

sure, but even omitting the phys ioctls gives you (a), which seems
like it is useful on it's own.  And would, I expect, be pretty useful
for the etnaviv folks working on r/e the gpu.

> What this is not about is fixing the crappy userspace GPU libraries and
> video decoding so that it's "secure" - not only is that way beyond my
> abilities, it would also be a violation of the closed source license to
> reverse engineer them so that were possible.

yeah, once you add the vendor gpu/etc drivers, if they are also giving
you a way to pass a phys addr, then plugging the holes in the drm/kms
driver won't do any good.  But in a way that is some other
non-upstream driver's problem.

> It is something that continues to be a problem and I'm making no claims
> that I'm fixing that.  So no, I will not put such a config option around
> it for the simple reason that by doing so, turning such an option off
> renders userspace utterly useless and the driver might as well not exist.
>
> If that means you want to NACK the patch, then fine; I'll just maintain
> it out of tree.  I did the driver mostly for my own personal benefit to
> get the Cubox to the point where I can boot it with or without the TV
> connected and have it work.  But it would be a shame if that meant
> that others could not benefit from about 8 solid months of my work on
> this and have to put up with crappy a fbdev driver.

I would like to get at least some of the driver upstream.  I'd hate
for it to have to live completely out of tree.  I can think of a
couple possibilities.

1) the best would be, if there was some way for the drm driver to know
the upper/lower bounds of the carveout, then it could bounds-check
against this.  And then in worst case, userspace can just screw up
other "graphics" buffers

2) if #1 weren't possible, then maybe just keep the phys ioctls as a
small patch on top of upstream.  The at least out of the box you get
modesetting.  I had to do something like this w/ omapdrm for gluing it
together w/ sgx/pvr stuff.  I re-arranged the code a bit to group all
the non-upstream bits together to avoid merge/rebase conflicts.

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 21:01             ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 21:01 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 4:08 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 03:59:34PM -0400, Rob Clark wrote:
>> On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > ARMADA_GEM_CREATE_PHYS is to deal with creating a gem buffer object for
>> > a chunk of physical memory allocated by other means (eg, the Vmeta stuff.)
>> > This allows my X server to remain compatible with the XF86 Dove driver,
>> > which permits the passing of the physical address of the video frame to
>> > the X server (otherwise we're into rewriting a whole load more code than
>> > is really desirable - and I really don't have time to rewrite bits of
>> > gstreamer and other stuff.)
>>
>> ahh, gotcha.. and, ugg, that is even worse..
>>
>> I'm not hugely a fan of giving userspace a way to dma into arbitrary
>> phys addresses (ie. create_phys + put_image).  But I don't need to
>> explain what you already know ;-)
>>
>> Is there a pre-defined carveout pool that you can at least bounds
>> check against?  Otherwise put this ioctl inside a #if
>> CONFIG_CRAZY_SOC_VENDOR_HOLE_TO_DRIVE_A_TRUCK_THROUGH / #endif..
>
> This driver is _not_ about supporting the GPU natively as part of the DRM,
> but providing the foundations for:
>
> (a) a properly functional interface to HDMI TVs (fbdev doesn't hack that)
>     with hotplug
> (b) providing sufficient infrastructure to allow video playback to work.

sure, but even omitting the phys ioctls gives you (a), which seems
like it is useful on it's own.  And would, I expect, be pretty useful
for the etnaviv folks working on r/e the gpu.

> What this is not about is fixing the crappy userspace GPU libraries and
> video decoding so that it's "secure" - not only is that way beyond my
> abilities, it would also be a violation of the closed source license to
> reverse engineer them so that were possible.

yeah, once you add the vendor gpu/etc drivers, if they are also giving
you a way to pass a phys addr, then plugging the holes in the drm/kms
driver won't do any good.  But in a way that is some other
non-upstream driver's problem.

> It is something that continues to be a problem and I'm making no claims
> that I'm fixing that.  So no, I will not put such a config option around
> it for the simple reason that by doing so, turning such an option off
> renders userspace utterly useless and the driver might as well not exist.
>
> If that means you want to NACK the patch, then fine; I'll just maintain
> it out of tree.  I did the driver mostly for my own personal benefit to
> get the Cubox to the point where I can boot it with or without the TV
> connected and have it work.  But it would be a shame if that meant
> that others could not benefit from about 8 solid months of my work on
> this and have to put up with crappy a fbdev driver.

I would like to get at least some of the driver upstream.  I'd hate
for it to have to live completely out of tree.  I can think of a
couple possibilities.

1) the best would be, if there was some way for the drm driver to know
the upper/lower bounds of the carveout, then it could bounds-check
against this.  And then in worst case, userspace can just screw up
other "graphics" buffers

2) if #1 weren't possible, then maybe just keep the phys ioctls as a
small patch on top of upstream.  The at least out of the box you get
modesetting.  I had to do something like this w/ omapdrm for gluing it
together w/ sgx/pvr stuff.  I re-arranged the code a bit to group all
the non-upstream bits together to avoid merge/rebase conflicts.

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 21:01             ` Rob Clark
@ 2013-06-10 21:15               ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 21:15 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 05:01:18PM -0400, Rob Clark wrote:
> I would like to get at least some of the driver upstream.  I'd hate
> for it to have to live completely out of tree.  I can think of a
> couple possibilities.
> 
> 1) the best would be, if there was some way for the drm driver to know
> the upper/lower bounds of the carveout, then it could bounds-check
> against this.  And then in worst case, userspace can just screw up
> other "graphics" buffers

The bounds check does what?  Check that the buffer object's physical
address is within another driver's range?  Fine, but all that it's
doing is preventing it being associated with a buffer object which can
_only_ be used for _read_ access by the LCD controller.  There is no
write access to the GEM objects by anything that this DRM driver talks
to.

So all it'll do is prevent you passing rogue addresses to the LCD
controller for scanout.

And how do we get that information into the driver from other random
drivers?  Hack in random inter-driver specific APIs to do it?  Come
up with yet another different way to try and identify whether a
particular resource is a block of reserved memory for this driver's
usage as a linear region or one of the others?  How.  Really, please
think about the problem.

The benefit vs the complexity involved really isn't worth it.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 21:15               ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 21:15 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jason Cooper, David Airlie, dri-devel, Darren Etheridge,
	linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 05:01:18PM -0400, Rob Clark wrote:
> I would like to get at least some of the driver upstream.  I'd hate
> for it to have to live completely out of tree.  I can think of a
> couple possibilities.
> 
> 1) the best would be, if there was some way for the drm driver to know
> the upper/lower bounds of the carveout, then it could bounds-check
> against this.  And then in worst case, userspace can just screw up
> other "graphics" buffers

The bounds check does what?  Check that the buffer object's physical
address is within another driver's range?  Fine, but all that it's
doing is preventing it being associated with a buffer object which can
_only_ be used for _read_ access by the LCD controller.  There is no
write access to the GEM objects by anything that this DRM driver talks
to.

So all it'll do is prevent you passing rogue addresses to the LCD
controller for scanout.

And how do we get that information into the driver from other random
drivers?  Hack in random inter-driver specific APIs to do it?  Come
up with yet another different way to try and identify whether a
particular resource is a block of reserved memory for this driver's
usage as a linear region or one of the others?  How.  Really, please
think about the problem.

The benefit vs the complexity involved really isn't worth it.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 15:57     ` Rob Clark
@ 2013-06-10 21:38       ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 21:38 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> <rmk+kernel@arm.linux.org.uk> wrote:
> > +/* The mode_config.mutex will be held for this call */
> > +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> > +       struct drm_framebuffer *old_fb)
> > +{
> > +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> > +       struct armada_regs regs[4];
> > +       unsigned i;
> > +
> > +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
> > +       armada_reg_queue_end(regs, i);
> > +
> > +       /* Wait for pending flips to complete */
> > +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
> > +
> > +       /* Take a reference to the new fb as we're using it */
> > +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
> 
> note that you probably want to ref/unref the fb (and let the fb hold a
> ref to the gem bo).. that will make life easier for planar formats too
> (as the fb should hold ref's to the bo for each plane)

Now changed - and it looks from my debug of gem_linear that it's working
correctly (iow, not leaking).

> > +struct drm_armada_gem_create {
> > +       uint32_t height;
> > +       uint32_t width;
> > +       uint32_t bpp;
> 
> just fwiw, typically height/width/bpp are properties of the fb but not
> the bo.. (except in some cases where kernel needs to know this to
> setup GTT correctly for tiled buffers)

Also fixed.

> > +struct drm_armada_gem_pwrite {
> > +       uint32_t handle;
> > +       uint32_t offset;
> > +       uint32_t size;
> 
> probably want a uint32_t padding here, or move the uint64_t up in the
> struct to avoid 32 vs 64b alignment differences.

And also fixed.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 21:38       ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 21:38 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jason Cooper, David Airlie, dri-devel, Darren Etheridge,
	linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> <rmk+kernel@arm.linux.org.uk> wrote:
> > +/* The mode_config.mutex will be held for this call */
> > +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> > +       struct drm_framebuffer *old_fb)
> > +{
> > +       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> > +       struct armada_regs regs[4];
> > +       unsigned i;
> > +
> > +       i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
> > +       armada_reg_queue_end(regs, i);
> > +
> > +       /* Wait for pending flips to complete */
> > +       wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
> > +
> > +       /* Take a reference to the new fb as we're using it */
> > +       drm_gem_object_reference(&drm_fb_obj(crtc->fb)->obj);
> 
> note that you probably want to ref/unref the fb (and let the fb hold a
> ref to the gem bo).. that will make life easier for planar formats too
> (as the fb should hold ref's to the bo for each plane)

Now changed - and it looks from my debug of gem_linear that it's working
correctly (iow, not leaking).

> > +struct drm_armada_gem_create {
> > +       uint32_t height;
> > +       uint32_t width;
> > +       uint32_t bpp;
> 
> just fwiw, typically height/width/bpp are properties of the fb but not
> the bo.. (except in some cases where kernel needs to know this to
> setup GTT correctly for tiled buffers)

Also fixed.

> > +struct drm_armada_gem_pwrite {
> > +       uint32_t handle;
> > +       uint32_t offset;
> > +       uint32_t size;
> 
> probably want a uint32_t padding here, or move the uint64_t up in the
> struct to avoid 32 vs 64b alignment differences.

And also fixed.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 11:10     ` Sebastian Hesselbarth
@ 2013-06-10 21:48       ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 21:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 01:10:18PM +0200, Sebastian Hesselbarth wrote:
> On 06/09/13 21:29, Russell King wrote:
>> +	/*
>> +	 * The spec is unclear about the polarities of the syncs.
>> +	 * We assume their non-inverted state is active high.
>> +	 */
>
> nit: "We confirmed their non-inverted state is active high."

Thanks, it's been a while since I looked at this so I had forgotten
to update the comment.  Now done.

>> +		if (resource_size(r) > SZ_4K)
>> +			mem = r;
>
> nit again: The register address window of Dove LCD is 64k although you  
> are right an no more than 512B are used. Also a comment would be nice,
> that everything above 4k (64k) is interpreted as gfx mem.

Fixed and comment added.

>> +	/* Create all LCD controllers */
>> +	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
>> +		struct clk *clk;
>> +
>> +		if (!res[n])
>> +			break;
>> +
>> +		clk = clk_get_sys("dovefb.0", "extclk");
>
> To be precise: the above should have the index at extclk as there
> is two extclk inputs that can be used for both lcdcs. So currently,
> as armada_crtc is hard-coding extclk0 input it should be
> "armadafb.%d", "extclk0".
>
> But I know, clocking in general will work-out with parent select for
> clk-mux and DT support.

I've sorted that out, but I'd forgotten there were three additional
patches on top of what I've posted sorting that stuff out - the
second one in particular:

4a5e9b7 DRM: armada: store kernel address for gem objects
f760c94 DRM: Dove: alternative variant based clocking
2e051fd DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB

Because they're scattered in other branches (the h/w cursor stuff
is separate) it's not trivial to generate a single series from git
for these.

>> +static const struct armada_output_type armada_drm_conn_slave = {
>> +	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
>
> For a rework of DRM slave encoder API, there should also be some way to
> get .connector_type and .encoder_type above from that slave encoder.
> IMHO it should be up to the slave encoder to determine connector and
> encoder type.

Encoder type - yes, but connector type doesn't seem sensible.  It's
possible for the TDA998x to be connected to a DVI connector - how
would the slave encoder know that?

>> diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
>> new file mode 100644
>> index 0000000..1b86696
>> --- /dev/null
>> +++ b/drivers/gpu/drm/armada/armada_slave.h
>> @@ -0,0 +1,26 @@
>> +/*
>> + * Copyright (C) 2012 Russell King
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +#ifndef ARMADA_TDA19988_H
>> +#define ARMADA_TDA19988_H
>
> nit: ARMADA_SLAVE_H

Nobbled. :)

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 21:48       ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 21:48 UTC (permalink / raw)
  To: Sebastian Hesselbarth
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, linux-arm-kernel

On Mon, Jun 10, 2013 at 01:10:18PM +0200, Sebastian Hesselbarth wrote:
> On 06/09/13 21:29, Russell King wrote:
>> +	/*
>> +	 * The spec is unclear about the polarities of the syncs.
>> +	 * We assume their non-inverted state is active high.
>> +	 */
>
> nit: "We confirmed their non-inverted state is active high."

Thanks, it's been a while since I looked at this so I had forgotten
to update the comment.  Now done.

>> +		if (resource_size(r) > SZ_4K)
>> +			mem = r;
>
> nit again: The register address window of Dove LCD is 64k although you  
> are right an no more than 512B are used. Also a comment would be nice,
> that everything above 4k (64k) is interpreted as gfx mem.

Fixed and comment added.

>> +	/* Create all LCD controllers */
>> +	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
>> +		struct clk *clk;
>> +
>> +		if (!res[n])
>> +			break;
>> +
>> +		clk = clk_get_sys("dovefb.0", "extclk");
>
> To be precise: the above should have the index at extclk as there
> is two extclk inputs that can be used for both lcdcs. So currently,
> as armada_crtc is hard-coding extclk0 input it should be
> "armadafb.%d", "extclk0".
>
> But I know, clocking in general will work-out with parent select for
> clk-mux and DT support.

I've sorted that out, but I'd forgotten there were three additional
patches on top of what I've posted sorting that stuff out - the
second one in particular:

4a5e9b7 DRM: armada: store kernel address for gem objects
f760c94 DRM: Dove: alternative variant based clocking
2e051fd DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB

Because they're scattered in other branches (the h/w cursor stuff
is separate) it's not trivial to generate a single series from git
for these.

>> +static const struct armada_output_type armada_drm_conn_slave = {
>> +	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
>
> For a rework of DRM slave encoder API, there should also be some way to
> get .connector_type and .encoder_type above from that slave encoder.
> IMHO it should be up to the slave encoder to determine connector and
> encoder type.

Encoder type - yes, but connector type doesn't seem sensible.  It's
possible for the TDA998x to be connected to a DVI connector - how
would the slave encoder know that?

>> diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
>> new file mode 100644
>> index 0000000..1b86696
>> --- /dev/null
>> +++ b/drivers/gpu/drm/armada/armada_slave.h
>> @@ -0,0 +1,26 @@
>> +/*
>> + * Copyright (C) 2012 Russell King
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +#ifndef ARMADA_TDA19988_H
>> +#define ARMADA_TDA19988_H
>
> nit: ARMADA_SLAVE_H

Nobbled. :)

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 21:48       ` Russell King - ARM Linux
@ 2013-06-10 21:56         ` Sebastian Hesselbarth
  -1 siblings, 0 replies; 120+ messages in thread
From: Sebastian Hesselbarth @ 2013-06-10 21:56 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/10/2013 11:48 PM, Russell King - ARM Linux wrote:
> On Mon, Jun 10, 2013 at 01:10:18PM +0200, Sebastian Hesselbarth wrote:
>> On 06/09/13 21:29, Russell King wrote:
>>> +static const struct armada_output_type armada_drm_conn_slave = {
>>> +	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
>>
>> For a rework of DRM slave encoder API, there should also be some way to
>> get .connector_type and .encoder_type above from that slave encoder.
>> IMHO it should be up to the slave encoder to determine connector and
>> encoder type.
>
> Encoder type - yes, but connector type doesn't seem sensible.  It's
> possible for the TDA998x to be connected to a DVI connector - how
> would the slave encoder know that?

True. But how should Armada DRM driver know? Actually, it is not
important where we put that information. IMHO it is more likely
to be known by TDA998x as by the corresponding master encoder.
With DT I'd suggest to have a property for TDA998x that sets either
HDMI A/B/C or DVI.

Not related to CuBox setup, but with DisplayPort it can even get worse,
e.g. consider a dumb LCDC connected to some DP++ (HDMI protocol over DP
electrical) encoder connected to a TMDS level shifter. Where do you put
the (final) connector type if not in the last encoder connected to that
chain?

Sebastian

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 21:56         ` Sebastian Hesselbarth
  0 siblings, 0 replies; 120+ messages in thread
From: Sebastian Hesselbarth @ 2013-06-10 21:56 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, linux-arm-kernel

On 06/10/2013 11:48 PM, Russell King - ARM Linux wrote:
> On Mon, Jun 10, 2013 at 01:10:18PM +0200, Sebastian Hesselbarth wrote:
>> On 06/09/13 21:29, Russell King wrote:
>>> +static const struct armada_output_type armada_drm_conn_slave = {
>>> +	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
>>
>> For a rework of DRM slave encoder API, there should also be some way to
>> get .connector_type and .encoder_type above from that slave encoder.
>> IMHO it should be up to the slave encoder to determine connector and
>> encoder type.
>
> Encoder type - yes, but connector type doesn't seem sensible.  It's
> possible for the TDA998x to be connected to a DVI connector - how
> would the slave encoder know that?

True. But how should Armada DRM driver know? Actually, it is not
important where we put that information. IMHO it is more likely
to be known by TDA998x as by the corresponding master encoder.
With DT I'd suggest to have a property for TDA998x that sets either
HDMI A/B/C or DVI.

Not related to CuBox setup, but with DisplayPort it can even get worse,
e.g. consider a dumb LCDC connected to some DP++ (HDMI protocol over DP
electrical) encoder connected to a TMDS level shifter. Where do you put
the (final) connector type if not in the last encoder connected to that
chain?

Sebastian

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 19:59         ` Rob Clark
@ 2013-06-10 22:01           ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-10 22:01 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
>> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
>>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
>>> <rmk+kernel@arm.linux.org.uk> wrote:
>>> > This patch adds support for the pair of LCD controllers on the Marvell
>>> > Armada 510 SoCs.  This driver supports:
>>> > - multiple contiguous scanout buffers for video and graphics
>>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>>> >   acceleration
>>> > - dual lcd0 and lcd1 crt operation
>>> > - video overlay on each LCD crt
>>>
>>> Any particular reason for not exposing the overlays as drm_plane's?
>>> That is probably something that should change before merging the
>>> driver.
>>
>> Only through not understanding much of DRM when I started this.
>> Provided DRM planes can do everything that I'm already doing with
>> the overlay support, then yes.  Otherwise, I want to stick with this
>> which is modelled after the i915 overlay interface.

Oh i915 overlays aren't a good reason ;-) I've done those way back
when drm didn't have any plane infrastructure and pretty much as my
very contribution. So totally lacked any clue.

If I can scrap together a bit of time I want to port the legacy
overlay code over to drm planes (and remap the current ioctl interface
to the drm plane interface for backwards compat). But atm that's
crushed under a giant pile of other todo items.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 22:01           ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-10 22:01 UTC (permalink / raw)
  To: Rob Clark
  Cc: linux-arm-kernel, Russell King - ARM Linux, Jason Cooper,
	dri-devel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
>> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
>>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
>>> <rmk+kernel@arm.linux.org.uk> wrote:
>>> > This patch adds support for the pair of LCD controllers on the Marvell
>>> > Armada 510 SoCs.  This driver supports:
>>> > - multiple contiguous scanout buffers for video and graphics
>>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>>> >   acceleration
>>> > - dual lcd0 and lcd1 crt operation
>>> > - video overlay on each LCD crt
>>>
>>> Any particular reason for not exposing the overlays as drm_plane's?
>>> That is probably something that should change before merging the
>>> driver.
>>
>> Only through not understanding much of DRM when I started this.
>> Provided DRM planes can do everything that I'm already doing with
>> the overlay support, then yes.  Otherwise, I want to stick with this
>> which is modelled after the i915 overlay interface.

Oh i915 overlays aren't a good reason ;-) I've done those way back
when drm didn't have any plane infrastructure and pretty much as my
very contribution. So totally lacked any clue.

If I can scrap together a bit of time I want to port the legacy
overlay code over to drm planes (and remap the current ioctl interface
to the drm plane interface for backwards compat). But atm that's
crushed under a giant pile of other todo items.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 22:01           ` Daniel Vetter
@ 2013-06-10 22:32             ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 22:32 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
> On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> > <linux@arm.linux.org.uk> wrote:
> >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> >>> <rmk+kernel@arm.linux.org.uk> wrote:
> >>> > This patch adds support for the pair of LCD controllers on the Marvell
> >>> > Armada 510 SoCs.  This driver supports:
> >>> > - multiple contiguous scanout buffers for video and graphics
> >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> >>> >   acceleration
> >>> > - dual lcd0 and lcd1 crt operation
> >>> > - video overlay on each LCD crt
> >>>
> >>> Any particular reason for not exposing the overlays as drm_plane's?
> >>> That is probably something that should change before merging the
> >>> driver.
> >>
> >> Only through not understanding much of DRM when I started this.
> >> Provided DRM planes can do everything that I'm already doing with
> >> the overlay support, then yes.  Otherwise, I want to stick with this
> >> which is modelled after the i915 overlay interface.
> 
> Oh i915 overlays aren't a good reason ;-) I've done those way back
> when drm didn't have any plane infrastructure and pretty much as my
> very contribution. So totally lacked any clue.
> 
> If I can scrap together a bit of time I want to port the legacy
> overlay code over to drm planes (and remap the current ioctl interface
> to the drm plane interface for backwards compat). But atm that's
> crushed under a giant pile of other todo items.

Aren't we all :(  The amount of time I have to touch this has reduced
substantially over the last couple of months, which is why there's been
a few weeks between the RFC's for this.

The issue here is that with the overlay interface, all I have to do
with one of these pass-by-phys-address things which the X server gets
is to:

1. associate the phys address with a gem object
2. pass it in via the overlay interface

With the DRM plane stuff, this gets more complicated:

1. associate the phys address with a gem object
2. call another driver private ioctl to bind the gem object to a framebuffer
   (there is no support for this in the generic ioctl API afaics) which
   has to allocate and setup a framebuffer
3. use setplane to update the plane

That all increases the expense of overlay, and adds further memory
allocations to the path (and frees when the previous framebuffer is
discarded.)

That all makes for higher load due to more CPU expense.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 22:32             ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 22:32 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: linux-arm-kernel, Rob Clark, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
> On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> > <linux@arm.linux.org.uk> wrote:
> >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> >>> <rmk+kernel@arm.linux.org.uk> wrote:
> >>> > This patch adds support for the pair of LCD controllers on the Marvell
> >>> > Armada 510 SoCs.  This driver supports:
> >>> > - multiple contiguous scanout buffers for video and graphics
> >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> >>> >   acceleration
> >>> > - dual lcd0 and lcd1 crt operation
> >>> > - video overlay on each LCD crt
> >>>
> >>> Any particular reason for not exposing the overlays as drm_plane's?
> >>> That is probably something that should change before merging the
> >>> driver.
> >>
> >> Only through not understanding much of DRM when I started this.
> >> Provided DRM planes can do everything that I'm already doing with
> >> the overlay support, then yes.  Otherwise, I want to stick with this
> >> which is modelled after the i915 overlay interface.
> 
> Oh i915 overlays aren't a good reason ;-) I've done those way back
> when drm didn't have any plane infrastructure and pretty much as my
> very contribution. So totally lacked any clue.
> 
> If I can scrap together a bit of time I want to port the legacy
> overlay code over to drm planes (and remap the current ioctl interface
> to the drm plane interface for backwards compat). But atm that's
> crushed under a giant pile of other todo items.

Aren't we all :(  The amount of time I have to touch this has reduced
substantially over the last couple of months, which is why there's been
a few weeks between the RFC's for this.

The issue here is that with the overlay interface, all I have to do
with one of these pass-by-phys-address things which the X server gets
is to:

1. associate the phys address with a gem object
2. pass it in via the overlay interface

With the DRM plane stuff, this gets more complicated:

1. associate the phys address with a gem object
2. call another driver private ioctl to bind the gem object to a framebuffer
   (there is no support for this in the generic ioctl API afaics) which
   has to allocate and setup a framebuffer
3. use setplane to update the plane

That all increases the expense of overlay, and adds further memory
allocations to the path (and frees when the previous framebuffer is
discarded.)

That all makes for higher load due to more CPU expense.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [RFC v3 0/4] rmk's Dove DRM/TDA19988 Cubox driver
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-10 22:47   ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 22:47 UTC (permalink / raw)
  To: linux-arm-kernel

Okay, so the previous set didn't contain all the updates I wanted.
That's partly because of the timespan between making those changes
and re-posting the RFC.

This time, the "Add Armada DRM driver" commit contains all the
updates it should've had from last time!

However, I'm posting a slightly different branch - it's rooted at
the same "Add Armada DRM driver" commit so take patch 1 from this
as an update to patch 2 of the previous series.

This looks like:
+  DRM: Armada: Add Armada DRM driver
|\
+ | drm/i2c: nxp-tda998x patches
+ | DRM: Armada: add support for drm tda19988 driver
  + DRM: Armada: Add support for hardware cursors
  + DRM: Armada: convert Armada hardware cursor support to RGB+transparency
  + DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB

Now, the three additional patches where incrementally add hardware
cursor support; first my original version.  This didn't work very well.
Next is my try with the RGB+transparency suggested in the first RFC
round.  The cursor image looked silly against some windows because
you can't sanely translate an alpha channel to transparency.

The last version is what I'm using, and I _am_ using hardware cursors.
This seems to be the best solution if we're going to have hardware
cursors, even though it means that we don't support 64x64 cursors.

For the first patch (the main large patch) the changes are as per my
reply to the previous review comments confirming the updates, and
a couple of other changes which should've been in the previous RFC:

1. if we map a gem object in kernel space, store its kernel space
   address.  This avoids use of phys_to_virt() in the cursor code
   which really wasn't nice.
2. initial variant-based clocking support to support non-Dove
   implementations.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [RFC v3 0/4] rmk's Dove DRM/TDA19988 Cubox driver
@ 2013-06-10 22:47   ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 22:47 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

Okay, so the previous set didn't contain all the updates I wanted.
That's partly because of the timespan between making those changes
and re-posting the RFC.

This time, the "Add Armada DRM driver" commit contains all the
updates it should've had from last time!

However, I'm posting a slightly different branch - it's rooted at
the same "Add Armada DRM driver" commit so take patch 1 from this
as an update to patch 2 of the previous series.

This looks like:
+  DRM: Armada: Add Armada DRM driver
|\
+ | drm/i2c: nxp-tda998x patches
+ | DRM: Armada: add support for drm tda19988 driver
  + DRM: Armada: Add support for hardware cursors
  + DRM: Armada: convert Armada hardware cursor support to RGB+transparency
  + DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB

Now, the three additional patches where incrementally add hardware
cursor support; first my original version.  This didn't work very well.
Next is my try with the RGB+transparency suggested in the first RFC
round.  The cursor image looked silly against some windows because
you can't sanely translate an alpha channel to transparency.

The last version is what I'm using, and I _am_ using hardware cursors.
This seems to be the best solution if we're going to have hardware
cursors, even though it means that we don't support 64x64 cursors.

For the first patch (the main large patch) the changes are as per my
reply to the previous review comments confirming the updates, and
a couple of other changes which should've been in the previous RFC:

1. if we map a gem object in kernel space, store its kernel space
   address.  This avoids use of phys_to_virt() in the cursor code
   which really wasn't nice.
2. initial variant-based clocking support to support non-Dove
   implementations.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 1/4] DRM: Armada: Add Armada DRM driver
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-10 22:51   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:48 UTC (permalink / raw)
  To: linux-arm-kernel; +Cc: Jason Cooper, dri-devel, Sebastian Hesselbarth

This patch adds support for the pair of LCD controllers on the Marvell
Armada 510 SoCs.  This driver supports:
- multiple contiguous scanout buffers for video and graphics
- shm backed cacheable buffer objects for X pixmaps for Vivante GPU
  acceleration
- dual lcd0 and lcd1 crt operation
- video overlay on each LCD crt
- page flipping of the main scanout buffers

Included in this commit is the core driver with no output support; output
support is platform and encoder driver dependent.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/Kconfig                 |    2 +
 drivers/gpu/drm/Makefile                |    1 +
 drivers/gpu/drm/armada/Kconfig          |   15 +
 drivers/gpu/drm/armada/Makefile         |    6 +
 drivers/gpu/drm/armada/armada_crtc.c    |  766 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/armada/armada_crtc.h    |   73 +++
 drivers/gpu/drm/armada/armada_debugfs.c |  186 ++++++++
 drivers/gpu/drm/armada/armada_drm.h     |   73 +++
 drivers/gpu/drm/armada/armada_drv.c     |  389 ++++++++++++++++
 drivers/gpu/drm/armada/armada_fb.c      |  156 +++++++
 drivers/gpu/drm/armada/armada_fb.h      |   21 +
 drivers/gpu/drm/armada/armada_fbdev.c   |  210 +++++++++
 drivers/gpu/drm/armada/armada_gem.c     |  435 ++++++++++++++++++
 drivers/gpu/drm/armada/armada_gem.h     |   43 ++
 drivers/gpu/drm/armada/armada_hw.h      |  308 +++++++++++++
 drivers/gpu/drm/armada/armada_ioctl.h   |  124 +++++
 drivers/gpu/drm/armada/armada_ioctlP.h  |   22 +
 drivers/gpu/drm/armada/armada_output.c  |  159 +++++++
 drivers/gpu/drm/armada/armada_output.h  |   38 ++
 drivers/gpu/drm/armada/armada_overlay.c |  514 +++++++++++++++++++++
 drivers/gpu/drm/armada/armada_slave.c   |  138 ++++++
 drivers/gpu/drm/armada/armada_slave.h   |   26 +
 drivers/gpu/drm/armada/drm_helper.h     |   31 ++
 23 files changed, 3736 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/armada/Kconfig
 create mode 100644 drivers/gpu/drm/armada/Makefile
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.c
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.h
 create mode 100644 drivers/gpu/drm/armada/armada_debugfs.c
 create mode 100644 drivers/gpu/drm/armada/armada_drm.h
 create mode 100644 drivers/gpu/drm/armada/armada_drv.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.h
 create mode 100644 drivers/gpu/drm/armada/armada_fbdev.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.h
 create mode 100644 drivers/gpu/drm/armada/armada_hw.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctl.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctlP.h
 create mode 100644 drivers/gpu/drm/armada/armada_output.c
 create mode 100644 drivers/gpu/drm/armada/armada_output.h
 create mode 100644 drivers/gpu/drm/armada/armada_overlay.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.h
 create mode 100644 drivers/gpu/drm/armada/drm_helper.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 1e82882..ae8a57f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
 
 source "drivers/gpu/drm/cirrus/Kconfig"
 
+source "drivers/gpu/drm/armada/Kconfig"
+
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d59b24..b458168 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
+obj-$(CONFIG_DRM_ARMADA) += armada/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-$(CONFIG_DRM_OMAP)	+= omapdrm/
diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
new file mode 100644
index 0000000..c7a0a94
--- /dev/null
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -0,0 +1,15 @@
+config DRM_ARMADA
+	tristate "DRM support for Marvell Armada SoCs"
+	depends on DRM && HAVE_CLK
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select DRM_KMS_HELPER
+	help
+	  Support the "LCD" controllers found on the Marvell Armada 510
+	  devices.  There are two controllers on the device, each controller
+	  supports graphics and video overlays.
+
+	  This driver provides no built-in acceleration; acceleration is
+	  performed by other IP found on the SoC.  This driver provides
+	  kernel mode setting and buffer management to userspace.
diff --git a/drivers/gpu/drm/armada/Makefile b/drivers/gpu/drm/armada/Makefile
new file mode 100644
index 0000000..430f025
--- /dev/null
+++ b/drivers/gpu/drm/armada/Makefile
@@ -0,0 +1,6 @@
+armada-y	:= armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
+		   armada_gem.o armada_output.o armada_overlay.o \
+		   armada_slave.o
+armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o
+
+obj-$(CONFIG_DRM_ARMADA) := armada.o
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
new file mode 100644
index 0000000..dadaf63
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -0,0 +1,766 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+
+struct armada_frame_work {
+	struct drm_pending_vblank_event *event;
+	struct armada_regs regs[4];
+	struct drm_framebuffer *old_fb;
+	struct work_struct work;
+};
+
+/*
+ * A note about interlacing.  Let's consider HDMI 1920x1080i.
+ * The timing parameters we have from X are:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640  1080 1084 1094 1125
+ * Which get translated to:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640   540  542  547  562
+ *
+ * This is how it is defined by CEA-861-D - line and pixel numbers are
+ * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
+ * line: 2640.  The odd frame, the first active line is at line 21, and
+ * the even frame, the first active line is 584.
+ *
+ * LN:    560     561     562     563             567     568    569
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
+ *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
+ *
+ * LN:    1123   1124    1125      1               5       6      7
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
+ *  23 blanking lines
+ *
+ * The Armada LCD Controller line and pixel numbers are, like X timings,
+ * referenced to the top left of the active frame.
+ *
+ * So, translating these to our LCD controller:
+ *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
+ *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
+ * Note: Vsync front porch remains constant!
+ *
+ * if (odd_frame) {
+ *   vtotal = mode->crtc_vtotal + 1;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
+ *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
+ * } else {
+ *   vtotal = mode->crtc_vtotal;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
+ *   vhorizpos = mode->crtc_hsync_start;
+ * }
+ * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
+ *
+ * So, we need to reprogram these registers on each vsync event:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *
+ * Note: we do not use the frame done interrupts because these appear
+ * to happen too early, and lead to jitter on the display (presumably
+ * they occur at the end of the last active line, before the vsync back
+ * porch, which we're reprogramming.)
+ */
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+void armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs)
+{
+	while (regs->offset != ~0) {
+		void __iomem *reg = dcrtc->base + regs->offset;
+		uint32_t val;
+
+		val = regs->mask;
+		if (val != 0)
+			val &= readl_relaxed(reg);
+		writel_relaxed(val | regs->val, reg);
+		++regs;
+	}
+}
+
+#define dpms_blanked(dpms)	((dpms) != DRM_MODE_DPMS_ON)
+
+static void armada_drm_crtc_update(struct armada_crtc *dcrtc)
+{
+	uint32_t dumb_ctrl;
+
+	dumb_ctrl = dcrtc->cfg_dumb_ctrl;
+
+	if (!dpms_blanked(dcrtc->dpms))
+		dumb_ctrl |= CFG_DUMB_ENA;
+
+	/*
+	 * When a dumb interface isn't under 24bit, it might be
+	 * under SPI or GPIO.  If set to 7, this will force
+	 * LCD_D[23:0] to output blank color and damage GPIO
+	 * and SPI behaviour.  So leave it as-is unless in
+	 * DUMB24_RGB888_0 mode.
+	 */
+	if (dpms_blanked(dcrtc->dpms) &&
+	    (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
+		dumb_ctrl &= ~DUMB_MASK;
+		dumb_ctrl |= DUMB_BLANK;
+	}
+
+	/*
+	 * The documentation doesn't indicate what the normal state
+	 * of the sync signals are.  Sebastian Hesselbart kindly probed
+	 * his hardware to determine their state.
+	 *
+	 * The non-inverted state of the sync signals is active high.
+	 * Setting these bits makes the appropriate signal active low.
+	 */
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC)
+		dumb_ctrl |= CFG_INV_CSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC)
+		dumb_ctrl |= CFG_INV_HSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC)
+		dumb_ctrl |= CFG_INV_VSYNC;
+
+	if (dcrtc->dumb_ctrl != dumb_ctrl) {
+		dcrtc->dumb_ctrl = dumb_ctrl;
+		writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+}
+
+static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb, int x, int y,
+	struct armada_regs *regs, bool interlaced)
+{
+	struct armada_gem_object *obj = drm_fb_obj(fb);
+	unsigned pitch = fb->pitches[0];
+	unsigned offset = y * pitch + x * fb->bits_per_pixel / 8;
+	uint32_t addr_odd, addr_even;
+	unsigned i = 0;
+
+	DRM_DEBUG_DRIVER("pitch %u x %d y %d bpp %d\n",
+		pitch, x, y, fb->bits_per_pixel);
+
+	addr_odd = addr_even = obj->dev_addr + offset;
+
+	if (interlaced) {
+		addr_even += pitch;
+		pitch *= 2;
+	}
+
+	/* write offset, base, and pitch */
+	armada_reg_queue_set(regs, i, addr_odd, LCD_CFG_GRA_START_ADDR0);
+	armada_reg_queue_set(regs, i, addr_even, LCD_CFG_GRA_START_ADDR1);
+	armada_reg_queue_mod(regs, i, pitch, 0xffff, LCD_CFG_GRA_PITCH);
+
+	return i;
+}
+
+static void armada_drm_unref_work(struct work_struct *_work)
+{
+	struct armada_frame_work *work =
+		container_of(_work, struct armada_frame_work, work);
+	drm_framebuffer_unreference(work->old_fb);
+	kfree(work);
+}
+
+static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
+	struct armada_frame_work *work)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	unsigned long flags;
+	int ret;
+
+	INIT_WORK(&work->work, armada_drm_unref_work);
+
+	ret = drm_vblank_get(dev, dcrtc->num);
+	if (ret) {
+		DRM_ERROR("failed to acquire vblank counter\n");
+		return ret;
+	}
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (!dcrtc->frame_work) {
+		dcrtc->frame_work = work;
+	} else {
+		ret = -EBUSY;
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+
+	if (ret)
+		drm_vblank_put(dev, dcrtc->num);
+
+	return ret;
+}
+
+static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	struct armada_frame_work *work = dcrtc->frame_work;
+
+	dcrtc->frame_work = NULL;
+
+	armada_drm_crtc_update_regs(dcrtc, work->regs);
+
+	if (work->event)
+		drm_send_vblank_event(dev, dcrtc->num, work->event);
+
+	drm_vblank_put(dev, dcrtc->num);
+
+	/* Finally, queue the process-half of the cleanup */
+	schedule_work(&work->work);
+}
+
+static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
+	struct drm_framebuffer *fb, bool force)
+{
+	struct armada_gem_object *obj;
+	struct armada_frame_work *work;
+
+	if (!fb)
+		return;
+
+	obj = drm_fb_obj(fb);
+	if (force) {
+		/* Display is disabled, so just drop the old fb */
+		drm_framebuffer_unreference(fb);
+		return;
+	}
+
+	work = kmalloc(sizeof *work, GFP_KERNEL);
+	if (work) {
+		int i = 0;
+		work->event = NULL;
+		work->old_fb = fb;
+		armada_reg_queue_end(work->regs, i);
+
+		if (armada_drm_crtc_queue_frame_work(dcrtc, work) == 0)
+			return;
+
+		kfree(work);
+	}
+
+	/*
+	 * Oops - just drop the reference immediately and hope for
+	 * the best.  The worst that will happen is the buffer gets
+	 * reused before it has finished being displayed.
+	 */
+	drm_framebuffer_unreference(fb);
+}
+
+static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+
+	/*
+	 * Tell the DRM core that vblank IRQs aren't going to happen for
+	 * a while.  This cleans up any pending vblank events for us.
+	 */
+	drm_vblank_off(dev, dcrtc->num);
+
+	/* Handle any pending flip event. */
+	spin_lock_irq(&dev->event_lock);
+	if (dcrtc->frame_work)
+		armada_drm_crtc_complete_frame_work(dcrtc);
+	spin_unlock_irq(&dev->event_lock);
+}
+
+void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b, int idx)
+{
+}
+
+void armada_drm_crtc_gamma_get(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, int idx)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_dpms(struct drm_crtc *crtc, int dpms)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != dpms) {
+		dcrtc->dpms = dpms;
+		armada_drm_crtc_update(dcrtc);
+		if (dpms_blanked(dpms))
+			armada_drm_vblank_off(dcrtc);
+	}
+}
+
+/*
+ * Prepare for a mode set.  Turn off overlay to ensure that we don't end
+ * up with the overlay size being bigger than the active screen size.
+ * We rely upon X refreshing this state after the mode set has completed.
+ *
+ * The mode_config.mutex will be held for this call
+ */
+static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	mutex_lock(&dev->struct_mutex);
+	if (dcrtc->overlay)
+		armada_drm_overlay_off(dev, dcrtc->overlay);
+	mutex_unlock(&dev->struct_mutex);
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_commit(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_ON) {
+		dcrtc->dpms = DRM_MODE_DPMS_ON;
+		armada_drm_crtc_update(dcrtc);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj)
+{
+	return true;
+}
+
+void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
+{
+	struct armada_vbl_event *e, *n;
+	void __iomem *base = dcrtc->base;
+
+	if (stat & DMA_FF_UNDERFLOW)
+		DRM_ERROR("video underflow on crtc %u\n", dcrtc->num);
+	if (stat & GRA_FF_UNDERFLOW)
+		DRM_ERROR("graphics underflow on crtc %u\n", dcrtc->num);
+
+	if (stat & VSYNC_IRQ)
+		drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num);
+
+	spin_lock(&dcrtc->irq_lock);
+
+	list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) {
+		list_del_init(&e->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		e->fn(dcrtc, e->data);
+	}
+
+	if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) {
+		int i = stat & GRA_FRAME_IRQ0 ? 0 : 1;
+		uint32_t val;
+
+		writel_relaxed(dcrtc->v[i].spu_v_porch, base + LCD_SPU_V_PORCH);
+		writel_relaxed(dcrtc->v[i].spu_v_h_total, base + LCD_SPUT_V_H_TOTAL);
+
+		val = readl_relaxed(base + LCD_SPU_ADV_REG);
+		val &= ~(0xfff << 20 | 0xfff);
+		val |= dcrtc->v[i].spu_adv_reg;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG);
+	}
+	spin_unlock(&dcrtc->irq_lock);
+
+	/* Only on frame 0 IRQs (start of progressive / odd frame) */
+	if (stat & GRA_FRAME_IRQ0) {
+		struct drm_device *dev = dcrtc->crtc.dev;
+
+		spin_lock(&dev->event_lock);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock(&dev->event_lock);
+
+		wake_up(&dcrtc->frame_wait);
+	}
+}
+
+/* These are locked by dev->vbl_lock */
+void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if (dcrtc->irq_ena & mask) {
+		dcrtc->irq_ena &= ~mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	}
+}
+
+void armada_drm_crtc_enable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if ((dcrtc->irq_ena & mask) != mask) {
+		dcrtc->irq_ena |= mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+		if (readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR) & mask)
+			writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
+	struct drm_display_mode *mode, struct drm_display_mode *adj,
+	int x, int y, struct drm_framebuffer *old_fb)
+{
+	struct armada_private *priv = crtc->dev->dev_private;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[16];
+	uint32_t lm, rm, tm, bm, val, sclk;
+	unsigned long flags;
+	unsigned i;
+	bool interlaced;
+	int ret;
+
+	/* First, check that this will succeed. */
+	ret = priv->ops->compute_crtc_clock(dcrtc, adj, NULL);
+	if (ret)
+		return ret;
+
+	drm_framebuffer_reference(crtc->fb);
+
+	interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
+
+	i = armada_drm_crtc_calc_fb(dcrtc->crtc.fb, x, y, regs, interlaced);
+
+	rm = adj->crtc_hsync_start - adj->crtc_hdisplay;
+	lm = adj->crtc_htotal - adj->crtc_hsync_end;
+	bm = adj->crtc_vsync_start - adj->crtc_vdisplay;
+	tm = adj->crtc_vtotal - adj->crtc_vsync_end;
+
+	DRM_DEBUG_DRIVER("H: %d %d %d %d lm %d rm %d\n",
+		adj->crtc_hdisplay,
+		adj->crtc_hsync_start,
+		adj->crtc_hsync_end,
+		adj->crtc_htotal, lm, rm);
+	DRM_DEBUG_DRIVER("V: %d %d %d %d tm %d bm %d\n",
+		adj->crtc_vdisplay,
+		adj->crtc_vsync_start,
+		adj->crtc_vsync_end,
+		adj->crtc_vtotal, tm, bm);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	drm_vblank_pre_modeset(crtc->dev, dcrtc->num);
+
+	crtc->mode = *adj;
+
+	val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA;
+	if (val != dcrtc->dumb_ctrl) {
+		dcrtc->dumb_ctrl = val;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+
+	/* Now compute the divider for real */
+	priv->ops->compute_crtc_clock(dcrtc, adj, &sclk);
+
+	armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV);
+
+	if (interlaced ^ dcrtc->interlaced) {
+		if (adj->flags & DRM_MODE_FLAG_INTERLACE)
+			drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+		else
+			drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		dcrtc->interlaced = interlaced;
+	}
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+
+	/* Even interlaced/progressive frame */
+	dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 |
+				    adj->crtc_htotal;
+	dcrtc->v[1].spu_v_porch = tm << 16 | bm;
+	val = adj->crtc_hsync_start;
+	dcrtc->v[1].spu_adv_reg = val << 20 | val;
+
+	if (interlaced) {
+		/* Odd interlaced frame */
+		dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total +
+						(1 << 16);
+		dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1;
+		val = adj->crtc_hsync_start - adj->crtc_htotal / 2;
+		dcrtc->v[0].spu_adv_reg = val << 20 | val;
+	} else {
+		dcrtc->v[0] = dcrtc->v[1];
+	}
+
+	val = (adj->crtc_vdisplay << 16) | adj->crtc_hdisplay;
+
+	armada_reg_queue_set(regs, i, val, LCD_SPU_V_H_ACTIVE);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GRA_HPXL_VLN);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GZM_HPXL_VLN);
+	armada_reg_queue_set(regs, i, (lm << 16) | rm, LCD_SPU_H_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_porch, LCD_SPU_V_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total,
+			   LCD_SPUT_V_H_TOTAL);
+	armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg | ADV_VSYNCOFFEN,
+			   (0xfff << 20 | 0xfff), LCD_SPU_ADV_REG);
+
+	val = dcrtc->cfg_dma_ctrl0;
+
+	switch (crtc->fb->pixel_format) {
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+		val |= CFG_GRA_1555;
+		break;
+	case DRM_FORMAT_RGB565:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR565:
+		val |= CFG_GRA_565;
+		break;
+	case DRM_FORMAT_RGB888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR888:
+		val |= CFG_GRA_888PACK;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR8888:
+		val |= CFG_GRA_X888;
+		break;
+	case DRM_FORMAT_ARGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_ABGR8888:
+		val |= CFG_GRA_8888;
+		break;
+	case DRM_FORMAT_C8:
+		val |= CFG_GRA_PSEUDO8 | CFG_PALETTE_ENA;
+		break;
+	}
+	if (interlaced)
+		val |= CFG_GRA_FTOGGLE;
+	armada_reg_queue_mod(regs, i, val, CFG_GRAFORMAT | CFG_GRA_SWAPRB |
+			   CFG_PALETTE_ENA | CFG_GRA_FTOGGLE,
+			   LCD_SPU_DMA_CTRL0);
+
+	val = adj->flags & DRM_MODE_FLAG_NVSYNC ? CFG_VSYNC_INV : 0;
+	armada_reg_queue_mod(regs, i, val, CFG_VSYNC_INV, LCD_SPU_DMA_CTRL1);
+
+	/*
+	 * Set the colorimetry, based upon the HDMI spec.
+	 * 1280x720p, 1920x1080p and 1920x1080i use ITU709, others
+	 * use ITU601.  In any case, we select professional.
+	 */
+	if ((adj->hdisplay == 1280 && adj->vdisplay == 720 && !interlaced) ||
+	    (adj->hdisplay == 1920 && adj->vdisplay == 1080)) {
+		val = CFG_CSC_PROF | CFG_CSC_CCIR709;
+	} else {
+		val = CFG_CSC_PROF;
+	}
+	armada_reg_queue_mod(regs, i, val, CFG_CSC_MASK, LCD_SPU_IOPAD_CONTROL);
+	armada_reg_queue_end(regs, i);
+
+	armada_drm_crtc_update_regs(dcrtc, regs);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+
+	armada_drm_crtc_update(dcrtc);
+
+	drm_vblank_post_modeset(crtc->dev, dcrtc->num);
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+	struct drm_framebuffer *old_fb)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[4];
+	unsigned i;
+
+	i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
+	armada_reg_queue_end(regs, i);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	/* Take a reference to the new fb as we're using it */
+	drm_framebuffer_reference(crtc->fb);
+
+	/* Update the base in the CRTC */
+	armada_drm_crtc_update_regs(dcrtc, regs);
+
+	/* Drop our previously held reference */
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+static void armada_drm_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_disable(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_OFF) {
+		dcrtc->dpms = DRM_MODE_DPMS_OFF;
+		armada_drm_crtc_update(dcrtc);
+	}
+	armada_drm_vblank_off(dcrtc);
+	armada_drm_crtc_finish_fb(dcrtc, crtc->fb, true);
+}
+
+static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
+	.dpms		= armada_drm_crtc_dpms,
+	.prepare	= armada_drm_crtc_prepare,
+	.commit		= armada_drm_crtc_commit,
+	.mode_fixup	= armada_drm_crtc_mode_fixup,
+	.mode_set	= armada_drm_crtc_mode_set,
+	.mode_set_base	= armada_drm_crtc_mode_set_base,
+	.load_lut	= armada_drm_crtc_load_lut,
+	.disable	= armada_drm_crtc_disable,
+};
+
+static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_private *priv = crtc->dev->dev_private;
+
+	priv->dcrtc[dcrtc->num] = NULL;
+	drm_crtc_cleanup(&dcrtc->crtc);
+
+	if (!IS_ERR(dcrtc->clk))
+		clk_disable_unprepare(dcrtc->clk);
+
+	kfree(dcrtc);
+}
+
+/*
+ * The mode_config lock is held here, to prevent races between this
+ * and a mode_set.
+ */
+static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
+	struct drm_framebuffer *fb, struct drm_pending_vblank_event *event)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_frame_work *work;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+	unsigned i;
+	int ret;
+
+	/* We don't support changing the pixel format */
+	if (fb->pixel_format != crtc->fb->pixel_format)
+		return -EINVAL;
+
+	work = kmalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return -ENOMEM;
+
+	work->event = event;
+	work->old_fb = dcrtc->crtc.fb;
+
+	i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs, dcrtc->interlaced);
+	armada_reg_queue_end(work->regs, i);
+
+	/*
+	 * Hold the old framebuffer for the work - DRM appears to drop our
+	 * reference to the old framebuffer in drm_mode_page_flip_ioctl().
+	 */
+	drm_framebuffer_reference(work->old_fb);
+
+	ret = armada_drm_crtc_queue_frame_work(dcrtc, work);
+	if (ret) {
+		/*
+		 * Undo our reference above; DRM does not drop the reference
+		 * to this object on error, so that's okay.
+		 */
+		drm_framebuffer_unreference(work->old_fb);
+		kfree(work);
+		return ret;
+	}
+
+	/*
+	 * Don't take a reference on the new framebuffer;
+	 * drm_mode_page_flip_ioctl() has already grabbed a reference and
+	 * will _not_ drop that reference on successful return from this
+	 * function.  Simply mark this new framebuffer as the current one.
+	 */
+	dcrtc->crtc.fb = fb;
+
+	/*
+	 * Finally, if the display is blanked, we won't receive an
+	 * interrupt, so complete it now.
+	 */
+	if (dpms_blanked(dcrtc->dpms)) {
+		spin_lock_irqsave(&dev->event_lock, flags);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+
+	return 0;
+}
+
+static struct drm_crtc_funcs armada_crtc_funcs = {
+	.destroy	= armada_drm_crtc_destroy,
+	.page_flip	= armada_drm_crtc_page_flip,
+	.set_config	= drm_crtc_helper_set_config,
+};
+
+int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
+	struct resource *res)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc;
+	void __iomem *base;
+
+	base = devm_request_and_ioremap(dev->dev, res);
+	if (!base) {
+		DRM_ERROR("failed to ioremap register\n");
+		return -ENOMEM;
+	}
+
+	dcrtc = kzalloc(sizeof(*dcrtc), GFP_KERNEL);
+	if (!dcrtc) {
+		DRM_ERROR("failed to allocate Armada crtc\n");
+		return -ENOMEM;
+	}
+
+	dcrtc->base = base;
+	dcrtc->num = num;
+	dcrtc->clk = ERR_PTR(-EINVAL);
+	dcrtc->cfg_dma_ctrl0 = CFG_GRA_ENA | CFG_GRA_HSMOOTH;
+	dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0;
+	spin_lock_init(&dcrtc->irq_lock);
+	dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR;
+	INIT_LIST_HEAD(&dcrtc->vbl_list);
+	init_waitqueue_head(&dcrtc->frame_wait);
+
+	/* Initialize some registers which we don't otherwise set */
+	writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_BLANKCOLOR);
+	writel_relaxed(CFG_IOPAD_DUMB24, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_SRAM_PARA0);
+	writel_relaxed(0x0000e000, dcrtc->base + LCD_SPU_SRAM_PARA1);
+	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
+
+	/* Lower the watermark so to eliminate jitter at higher bandwidths */
+	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
+
+	/* Ensure AXI pipeline is enabled */
+	armada_updatel(CFG_ARBFAST_ENA, 0, dcrtc->base + LCD_SPU_DMA_CTRL0);
+
+	priv->dcrtc[dcrtc->num] = dcrtc;
+
+	drm_crtc_init(dev, &dcrtc->crtc, &armada_crtc_funcs);
+	drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
new file mode 100644
index 0000000..e00dcf5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CRTC_H
+#define ARMADA_CRTC_H
+
+struct armada_overlay;
+struct armada_gem_object;
+
+struct armada_regs {
+	uint32_t offset;
+	uint32_t mask;
+	uint32_t val;
+};
+
+#define armada_reg_queue_mod(_r, _i, _v, _m, _o)	\
+	do {					\
+		struct armada_regs *__reg = _r;	\
+		__reg[_i].offset = _o;		\
+		__reg[_i].mask = ~(_m);		\
+		__reg[_i].val = _v;		\
+		_i++;				\
+	} while (0)
+
+#define armada_reg_queue_set(_r, _i, _v, _o)	\
+	armada_reg_queue_mod(_r, _i, _v, ~0, _o)
+
+#define armada_reg_queue_end(_r, _i)		\
+	armada_reg_queue_mod(_r, _i, 0, 0, ~0)
+
+struct armada_frame_work;
+
+struct armada_crtc {
+	struct drm_crtc		crtc;
+	unsigned		num;
+	void __iomem		*base;
+	struct clk		*clk;
+	struct {
+		uint32_t	spu_v_h_total;
+		uint32_t	spu_v_porch;
+		uint32_t	spu_adv_reg;
+	} v[2];
+	bool			interlaced;
+
+	struct armada_overlay	*overlay;
+
+	int			dpms;
+	uint32_t		cfg_dma_ctrl0;
+	uint32_t		cfg_dumb_ctrl;
+	uint32_t		dumb_ctrl;
+
+	wait_queue_head_t	frame_wait;
+	struct armada_frame_work	*frame_work;
+
+	spinlock_t		irq_lock;
+	uint32_t		irq_ena;
+	struct list_head	vbl_list;
+};
+#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)
+
+int armada_drm_crtc_create(struct drm_device *, unsigned, struct resource *);
+void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
+void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
+void armada_drm_crtc_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_debugfs.c b/drivers/gpu/drm/armada/armada_debugfs.c
new file mode 100644
index 0000000..ff5ada3
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_debugfs.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <drm/drmP.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+
+static int armada_debugfs_gem_offs_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct drm_gem_mm *mm = dev->mm_private;
+
+	return drm_mm_dump_table(m, &mm->offset_manager);
+}
+
+static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct armada_private *priv = node->minor->dev->dev_private;
+
+	return drm_mm_dump_table(m, &priv->linear);
+}
+
+static int armada_debugfs_reg_show(struct seq_file *m, void *data)
+{
+	struct drm_device *dev = m->private;
+	struct armada_private *priv = dev->dev_private;
+	int n, i;
+
+	if (priv) {
+		for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+			struct armada_crtc *dcrtc = priv->dcrtc[n];
+			if (!dcrtc)
+				continue;
+
+			for (i = 0x84; i <= 0x1c4; i += 4) {
+				uint32_t v = readl_relaxed(dcrtc->base + i);
+				seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, armada_debugfs_reg_show, inode->i_private);
+}
+
+static const struct file_operations fops_reg_r = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_r_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int armada_debugfs_write(struct file *file, const char __user *ptr,
+	size_t len, loff_t *off)
+{
+	struct drm_device *dev = file->private_data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	char buf[32], *p;
+	uint32_t reg, val;
+	int ret;
+
+	if (*off != 0)
+		return 0;
+
+	if (len > sizeof(buf) - 1)
+		len = sizeof(buf) - 1;
+
+	ret = strncpy_from_user(buf, ptr, len);
+	if (ret < 0)
+		return ret;
+	buf[len] = '\0';
+
+	reg = simple_strtoul(buf, &p, 16);
+	if (!isspace(*p))
+		return -EINVAL;
+	val = simple_strtoul(p + 1, NULL, 16);
+
+	if (reg >= 0x84 && reg <= 0x1c4)
+		writel(val, dcrtc->base + reg);
+
+	return len;
+}
+
+static int armada_debugfs_reg_w_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static const struct file_operations fops_reg_w = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_w_open,
+	.write = armada_debugfs_write,
+	.llseek = noop_llseek,
+};
+
+static struct drm_info_list armada_debugfs_list[] = {
+	{ "gem_linear", armada_debugfs_gem_linear_show, 0 },
+	{ "gem_offset", armada_debugfs_gem_offs_show, 0 },
+};
+#define ARMADA_DEBUGFS_ENTRIES ARRAY_SIZE(armada_debugfs_list)
+
+static int drm_add_fake_info_node(struct drm_minor *minor, struct dentry *ent,
+	const void *key)
+{
+	struct drm_info_node *node;
+
+	node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL);
+	if (node == NULL) {
+		debugfs_remove(ent);
+		return -ENOMEM;
+	}
+
+	node->minor = minor;
+	node->dent = ent;
+	node->info_ent = (void *) key;
+
+	mutex_lock(&minor->debugfs_lock);
+	list_add(&node->list, &minor->debugfs_list);
+	mutex_unlock(&minor->debugfs_lock);
+
+	return 0;
+}
+
+static int armada_debugfs_create(struct dentry *root, struct drm_minor *minor,
+	const char *name, umode_t mode, const struct file_operations *fops)
+{
+	struct dentry *de;
+
+	de = debugfs_create_file(name, mode, root, minor->dev, fops);
+
+	return drm_add_fake_info_node(minor, de, fops);
+}
+
+int armada_drm_debugfs_init(struct drm_minor *minor)
+{
+	int ret;
+
+	ret = drm_debugfs_create_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				       minor->debugfs_root, minor);
+	if (ret)
+		return ret;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				   "reg", S_IFREG | S_IRUSR, &fops_reg_r);
+	if (ret)
+		goto err_1;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				"reg_wr", S_IFREG | S_IWUSR, &fops_reg_w);
+	if (ret)
+		goto err_2;
+	return ret;
+
+ err_2:
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+ err_1:
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+	return ret;
+}
+
+void armada_drm_debugfs_cleanup(struct drm_minor *minor)
+{
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_w, 1, minor);
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+}
diff --git a/drivers/gpu/drm/armada/armada_drm.h b/drivers/gpu/drm/armada/armada_drm.h
new file mode 100644
index 0000000..c2dcde5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drm.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_DRM_H
+#define ARMADA_DRM_H
+
+struct armada_crtc;
+struct armada_gem_object;
+struct armada_overlay;
+struct clk;
+
+static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp)
+{
+	uint32_t pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2;
+
+	/* 88AP510 spec recommends pitch be a multiple of 128 */
+	return ALIGN(pitch, 128);
+}
+
+struct armada_vbl_event {
+	struct list_head	node;
+	void			*data;
+	void			(*fn)(struct armada_crtc *, void *);
+};
+void armada_drm_vbl_event_add(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *, struct armada_vbl_event *);
+#define armada_drm_vbl_event_init(_e,_f,_d) do {	\
+	struct armada_vbl_event *__e = _e;	\
+	INIT_LIST_HEAD(&__e->node);		\
+	__e->data = _d;				\
+	__e->fn = _f;				\
+} while (0)
+
+
+struct armada_private;
+
+struct armada_ops {
+	int (*init)(struct armada_private *, struct device *);
+	int (*compute_crtc_clock)(struct armada_crtc *,
+				  const struct drm_display_mode *,
+				  uint32_t *);
+};
+
+struct armada_private {
+	const struct armada_ops	*ops;
+	struct drm_fb_helper	*fbdev;
+	struct armada_crtc	*dcrtc[2];
+	struct armada_overlay	*overlay;
+	struct drm_mm		linear;
+	struct clk		*extclk[2];
+#ifdef CONFIG_DEBUG_FS
+	struct dentry		*de;
+#endif
+};
+
+extern const struct drm_mode_config_funcs armada_drm_mode_config_funcs;
+
+int armada_fbdev_init(struct drm_device *);
+void armada_fbdev_fini(struct drm_device *);
+
+int armada_overlay_create(struct drm_device *);
+void armada_overlay_destroy(struct drm_device *);
+void armada_drm_overlay_off(struct drm_device *, struct armada_overlay *);
+
+int armada_drm_debugfs_init(struct drm_minor *);
+void armada_drm_debugfs_cleanup(struct drm_minor *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
new file mode 100644
index 0000000..807fd35
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+/*
+ * Armada510 specific initialization.  There _may_ be two external
+ * clocks - look them up now but don't fail if they're not present.
+ * We may be able to use the internal clocks instead.
+ */
+static int armada510_init(struct armada_private *priv, struct device *dev)
+{
+	priv->extclk[0] = devm_clk_get(dev, "ext_ref_clk_1");
+
+	if (IS_ERR(priv->extclk[0])) {
+		int ret = PTR_ERR(priv->extclk[0]);
+		if (ret == -ENOENT)
+			ret = -EPROBE_DEFER;
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Armada510 specific SCLK register selection: this gets called with
+ * sclk = NULL to test whether the mode is supportable, and again with
+ * sclk != NULL to set the clocks up for that.  The former can return
+ * an error, but the latter is expected not to.
+ *
+ * We currently are pretty rudimentary here, always selecting
+ * EXT_REF_CLK_1 for LCD0 and erroring LCD1.  This needs improvement!
+ */
+static int armada510_compute_crtc_clock(struct armada_crtc *dcrtc,
+	const struct drm_display_mode *mode, uint32_t *sclk)
+{
+	struct armada_private *priv = dcrtc->crtc.dev->dev_private;
+	struct clk *clk = priv->extclk[0];
+	int ret;
+
+	if (dcrtc->num == 1)
+		return -EINVAL;
+
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	if (dcrtc->clk != clk) {
+		ret = clk_prepare_enable(clk);
+		if (ret)
+			return ret;
+		dcrtc->clk = clk;
+	}
+
+	if (sclk) {
+		uint32_t rate, ref, div;
+
+		rate = mode->clock * 1000;
+		ref = clk_round_rate(clk, rate);
+		div = DIV_ROUND_UP(ref, rate);
+		if (div < 1)
+			div = 1;
+
+		clk_set_rate(clk, ref);
+		*sclk = div | SCLK_510_EXTCLK1;
+	}
+
+	return 0;
+}
+
+static const struct armada_ops armada510_ops = {
+	.init = armada510_init,
+	.compute_crtc_clock = armada510_compute_crtc_clock,
+};
+
+static int armada_drm_load(struct drm_device *dev, unsigned long flags)
+{
+	struct armada_private *priv;
+	struct resource *res[ARRAY_SIZE(priv->dcrtc)];
+	struct resource *mem = NULL;
+	int ret, n, i;
+
+	memset(res, 0, sizeof(res));
+
+	for (n = i = 0; ; n++) {
+		struct resource *r = platform_get_resource(dev->platformdev,
+							   IORESOURCE_MEM, n);
+		if (!r)
+			break;
+
+		/* Resources above 64K are graphics memory */
+		if (resource_size(r) > SZ_64K)
+			mem = r;
+		else if (i < ARRAY_SIZE(priv->dcrtc))
+			res[i++] = r;
+		else
+			return -EINVAL;
+	}
+
+	if (!res[0] || !mem)
+		return -ENXIO;
+
+	if (!devm_request_mem_region(dev->dev, mem->start,
+			resource_size(mem), "armada-drm"))
+		return -EBUSY;
+
+	priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		DRM_ERROR("failed to allocate private\n");
+		ret = -ENOMEM;
+		goto err_buf;
+	}
+
+	dev->dev_private = priv;
+
+	priv->ops = &armada510_ops;
+
+	ret = priv->ops->init(priv, dev->dev);
+	if (ret)
+		return ret;
+
+	/* Mode setting support */
+	drm_mode_config_init(dev);
+	dev->mode_config.min_width = 0;
+	dev->mode_config.min_height = 0;
+	dev->mode_config.max_width = 2048;
+	dev->mode_config.max_height = 2048;
+	dev->mode_config.preferred_depth = 24;
+	dev->mode_config.funcs = &armada_drm_mode_config_funcs;
+	drm_mm_init(&priv->linear, mem->start, resource_size(mem));
+
+	/* Create all LCD controllers */
+	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+		if (!res[n])
+			break;
+
+		ret = armada_drm_crtc_create(dev, n, res[n]);
+		if (ret)
+			goto err_kms;
+	}
+
+	ret = drm_vblank_init(dev, n);
+	if (ret)
+		goto err_kms;
+
+	ret = drm_irq_install(dev);
+	if (ret)
+		goto err_kms;
+
+	dev->vblank_disable_allowed = 1;
+
+	ret = armada_fbdev_init(dev);
+	if (ret)
+		goto err_irq;
+
+	ret = armada_overlay_create(dev);
+	if (ret)
+		goto err_irq;
+
+	drm_kms_helper_poll_init(dev);
+
+	return 0;
+
+ err_irq:
+	drm_irq_uninstall(dev);
+ err_kms:
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+ err_buf:
+
+	return ret;
+}
+
+static int armada_drm_unload(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+
+	drm_kms_helper_poll_fini(dev);
+	armada_overlay_destroy(dev);
+	armada_fbdev_fini(dev);
+	drm_irq_uninstall(dev);
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+	dev->dev_private = NULL;
+
+	return 0;
+}
+
+void armada_drm_vbl_event_add(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	if (list_empty(&evt->node)) {
+		list_add_tail(&evt->node, &dcrtc->vbl_list);
+
+		drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+	}
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+void armada_drm_vbl_event_remove(struct armada_crtc *dcrtc,
+	struct armada_vbl_event *evt)
+{
+	if (!list_empty(&evt->node)) {
+		list_del_init(&evt->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+	}
+}
+
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	armada_drm_vbl_event_remove(dcrtc, evt);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+/* These are called under the vbl_lock. */
+static int armada_drm_enable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_enable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+	return 0;
+}
+
+static void armada_drm_disable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_disable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+}
+
+static irqreturn_t armada_drm_irq_handler(int irq, void *arg)
+{
+	struct drm_device *dev = arg;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	uint32_t v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);
+	irqreturn_t handled = IRQ_NONE;
+
+	/*
+	 * This is rediculous - rather than writing bits to clear, we
+	 * have to set the actual status register value.  This is racy.
+	 */
+	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+
+	/* Mask out those interrupts we haven't enabled */
+	v = stat & dcrtc->irq_ena;
+
+	if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) {
+		armada_drm_crtc_irq(dcrtc, stat);
+		handled = IRQ_HANDLED;
+	}
+
+	return handled;
+}
+
+static int armada_drm_irq_postinstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	spin_lock_irq(&dev->vbl_lock);
+	writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	spin_unlock_irq(&dev->vbl_lock);
+
+	return 0;
+}
+
+static void armada_drm_irq_uninstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ENA);
+}
+
+static struct drm_ioctl_desc armada_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_MMAP, armada_gem_mmap_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PROP, armada_gem_prop_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PWRITE, armada_gem_pwrite_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_PUT_IMAGE, armada_overlay_put_image_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_ATTRS, armada_overlay_attrs_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+};
+
+static const struct file_operations armada_drm_fops = {
+	.owner			= THIS_MODULE,
+	.llseek			= no_llseek,
+	.read			= drm_read,
+	.poll			= drm_poll,
+	.unlocked_ioctl		= drm_ioctl,
+	.mmap			= drm_gem_mmap,
+	.open			= drm_open,
+	.release		= drm_release,
+	.fasync			= drm_fasync,
+};
+
+extern const struct vm_operations_struct armada_gem_vm_ops;
+
+static struct drm_driver armada_drm_driver = {
+	.load			= armada_drm_load,
+	.open			= NULL,
+	.preclose		= NULL,
+	.postclose		= NULL,
+	.lastclose		= NULL,
+	.unload			= armada_drm_unload,
+	.get_vblank_counter	= drm_vblank_count,
+	.enable_vblank		= armada_drm_enable_vblank,
+	.disable_vblank		= armada_drm_disable_vblank,
+	.irq_handler		= armada_drm_irq_handler,
+	.irq_postinstall	= armada_drm_irq_postinstall,
+	.irq_uninstall		= armada_drm_irq_uninstall,
+#ifdef CONFIG_DEBUG_FS
+	.debugfs_init		= armada_drm_debugfs_init,
+	.debugfs_cleanup	= armada_drm_debugfs_cleanup,
+#endif
+	.gem_init_object	= NULL,
+	.gem_free_object	= armada_gem_free_object,
+	.prime_handle_to_fd	= NULL,
+	.prime_fd_to_handle	= NULL,
+	.gem_prime_export	= NULL,
+	.gem_prime_import	= NULL,
+	.dumb_create		= armada_gem_dumb_create,
+	.dumb_map_offset	= armada_gem_dumb_map_offset,
+	.dumb_destroy		= armada_gem_dumb_destroy,
+	.gem_vm_ops		= &armada_gem_vm_ops,
+	.major			= 1,
+	.minor			= 0,
+	.name			= "armada-drm",
+	.desc			= "Armada SoC DRM",
+	.date			= "20120730",
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_HAVE_IRQ,
+	.ioctls			= armada_ioctls,
+	.fops			= &armada_drm_fops,
+};
+
+static int armada_drm_probe(struct platform_device *pdev)
+{
+	return drm_platform_init(&armada_drm_driver, pdev);
+}
+
+static int armada_drm_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&armada_drm_driver, pdev);
+	return 0;
+}
+
+static struct platform_driver armada_drm_platform_driver = {
+	.probe	= armada_drm_probe,
+	.remove	= armada_drm_remove,
+	.driver	= {
+		.owner	= THIS_MODULE,
+		.name	= "armada-drm",
+	},
+};
+
+static int __init armada_drm_init(void)
+{
+	armada_drm_driver.num_ioctls = DRM_ARRAY_SIZE(armada_ioctls);
+	return platform_driver_register(&armada_drm_platform_driver);
+}
+module_init(armada_drm_init);
+
+static void __exit armada_drm_exit(void)
+{
+	platform_driver_unregister(&armada_drm_platform_driver);
+}
+module_exit(armada_drm_exit);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Armada DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:armada-drm");
diff --git a/drivers/gpu/drm/armada/armada_fb.c b/drivers/gpu/drm/armada/armada_fb.c
new file mode 100644
index 0000000..8f72f13
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static void armada_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+
+	drm_framebuffer_cleanup(&dfb->fb);
+	if (dfb->obj)
+		drm_gem_object_unreference_unlocked(&dfb->obj->obj);
+	kfree(dfb);
+}
+
+static int armada_fb_create_handle(struct drm_framebuffer *fb,
+	struct drm_file *dfile, unsigned int *handle)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+	return drm_gem_handle_create(dfile, &dfb->obj->obj, handle);
+}
+
+static const struct drm_framebuffer_funcs armada_fb_funcs = {
+	.destroy	= armada_fb_destroy,
+	.create_handle	= armada_fb_create_handle,
+};
+
+/*
+ * Supported pixel formats:
+ *		T	R	G	B	BPP
+ * RGB565 		[15:11] [10:5]	[4:0]	16	DRM_FORMAT_RGB565
+ * BGR565		[4:0]	[10:5]	[15:11]	16	DRM_FORMAT_BGR565
+ * RGB1555	15	[14:10]	[9:5]	[4:0]	16	DRM_FORMAT_ARGB1555
+ * BGR1555	15	[4:0]	[9:5]	[14:10]	16	DRM_FORMAT_ABGR1555
+ * RGB888PACK		[23:16]	[15:8]	[7:0]	24	DRM_FORMAT_RGB888
+ * BGR888PACK		[7:0]	[15:8]	[23:16]	24	DRM_FORMAT_BGR888
+ * RGB888UNPACK		[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_XRGB8888
+ * BGR888UNPACK		[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_XBGR8888
+ * RGBA888	[31:24]	[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_ARGB8888
+ * BGRA888	[31:24]	[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_ABGR8888
+ *
+ * We don't currently support (note that the YUV pixel fields
+ * are incorrect, coming from the dovefb driver):
+ *			Y	U	V	BPP
+ * YUV422PACK		[15:8]	[7:4]	[3:0]	16
+ * YVU422PACK		[7:0]	[11:8]	[15:12]	16
+ * YUV422PLANAR		[15:8]	[7:4]	[3:0]	16
+ * YVU422PLANAR		[7:0]	[11:8]	[15:12]	16
+ * YUV420PLANAR		[11:4]	[3:2]	[1:0]	12
+ * YVU420PLANAR		[7:0]	[9:8]	[11:10]	12
+ * PSEUDOCOLOR					8
+ * UYVY422PACK		[11:4]	[15:12]	[3:0]	16
+ */
+int armada_framebuffer_create(struct drm_device *dev,
+	struct armada_framebuffer **dfbp, struct drm_mode_fb_cmd2 *mode,
+	struct armada_gem_object *obj)
+{
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	switch (mode->pixel_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
+	if (!dfb) {
+		DRM_ERROR("failed to allocate Armada fb object\n");
+		return -ENOMEM;
+	}
+
+	ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs);
+	if (ret) {
+		kfree(dfb);
+		return ret;
+	}
+
+	drm_helper_mode_fill_fb_struct(&dfb->fb, mode);
+
+	/*
+	 * Take a reference on our object - the caller is expected
+	 * to drop their reference to it.
+	 */
+	drm_gem_object_reference(&obj->obj);
+	dfb->obj = obj;
+	*dfbp = dfb;
+
+	return 0;
+}
+
+static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
+	struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode)
+{
+	struct armada_gem_object *obj;
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n",
+		mode->width, mode->height, mode->pixel_format,
+		mode->flags, mode->pitches[0], mode->pitches[1],
+		mode->pitches[2]);
+
+	/* We can only handle a single plane at the moment */
+	if (drm_format_num_planes(mode->pixel_format) > 1)
+		return ERR_PTR(-EINVAL);
+
+	obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		return ERR_PTR(-ENOENT);
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, mode, obj);
+	drm_gem_object_unreference_unlocked(&obj->obj);
+
+	if (ret) {
+		DRM_ERROR("failed to initialize framebuffer\n");
+		return ERR_PTR(ret);
+	}
+
+	return &dfb->fb;
+}
+
+static void armada_output_poll_changed(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh)
+		drm_fb_helper_hotplug_event(fbh);
+}
+
+const struct drm_mode_config_funcs armada_drm_mode_config_funcs = {
+	.fb_create		= armada_fb_create,
+	.output_poll_changed	= armada_output_poll_changed,
+};
diff --git a/drivers/gpu/drm/armada/armada_fb.h b/drivers/gpu/drm/armada/armada_fb.h
new file mode 100644
index 0000000..0c78d2b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_FB_H
+#define ARMADA_FB_H
+
+struct armada_framebuffer {
+	struct drm_framebuffer	fb;
+	struct armada_gem_object	*obj;
+};
+#define drm_fb_to_armada_fb(dfb) container_of(dfb, struct armada_framebuffer, fb)
+#define drm_fb_obj(fb) drm_fb_to_armada_fb(fb)->obj
+
+int armada_framebuffer_create(struct drm_device *, struct armada_framebuffer **,
+	struct drm_mode_fb_cmd2 *, struct armada_gem_object *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_fbdev.c b/drivers/gpu/drm/armada/armada_fbdev.c
new file mode 100644
index 0000000..bfacdd0
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fbdev.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Written from the i915 driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static /*const*/ struct fb_ops armada_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_check_var	= drm_fb_helper_check_var,
+	.fb_set_par	= drm_fb_helper_set_par,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_pan_display	= drm_fb_helper_pan_display,
+	.fb_blank	= drm_fb_helper_blank,
+	.fb_setcmap	= drm_fb_helper_setcmap,
+	.fb_debug_enter	= drm_fb_helper_debug_enter,
+	.fb_debug_leave	= drm_fb_helper_debug_leave,
+};
+
+static int armada_fb_create(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	struct drm_device *dev = fbh->dev;
+	struct drm_mode_fb_cmd2 mode;
+	struct armada_framebuffer *dfb;
+	struct armada_gem_object *obj;
+	struct fb_info *info;
+	int size, ret;
+	void *ptr;
+
+	memset(&mode, 0, sizeof(mode));
+	mode.width = sizes->surface_width;
+	mode.height = sizes->surface_height;
+	mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp);
+	mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+					sizes->surface_depth);
+
+	size = ALIGN(mode.pitches[0] * mode.height, PAGE_SIZE);
+	obj = armada_gem_alloc_private_object(dev, size);
+	if (!obj) {
+		DRM_ERROR("failed to allocate fb memory\n");
+		return -ENOMEM;
+	}
+
+	ret = armada_gem_linear_back(dev, obj);
+	if (ret) {
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return ret;
+	}
+
+	ptr = armada_gem_map_object(dev, obj);
+	if (!ptr) {
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return -ENOMEM;
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, &mode, obj);
+	if (ret)
+		goto err_fbcreate;
+
+	mutex_lock(&dev->struct_mutex);
+
+	info = framebuffer_alloc(0, dev->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto err_fballoc;
+	}
+
+	ret = fb_alloc_cmap(&info->cmap, 256, 0);
+	if (ret) {
+		ret = -ENOMEM;
+		goto err_fbcmap;
+	}
+
+	strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id));
+	info->par = fbh;
+	info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+	info->fbops = &armada_fb_ops;
+	info->fix.smem_start = obj->phys_addr;
+	info->fix.smem_len = size;
+	info->screen_size = size;
+	info->screen_base = ptr;
+	fbh->fb = &dfb->fb;
+	fbh->fbdev = info;
+	drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth);
+	drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height);
+
+	DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08x\n",
+		dfb->fb.width, dfb->fb.height,
+		dfb->fb.bits_per_pixel, obj->phys_addr);
+
+	/* Reference is now held by the framebuffer object */
+	drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+
+	return 0;
+
+ err_fbcmap:
+	framebuffer_release(info);
+ err_fballoc:
+	mutex_unlock(&dev->struct_mutex);
+	dfb->fb.funcs->destroy(&dfb->fb);
+ err_fbcreate:
+	drm_gem_object_unreference_unlocked(&obj->obj);
+	return ret;
+}
+
+static void armada_fb_destroy(struct drm_fb_helper *fbh)
+{
+	struct fb_info *info = fbh->fbdev;
+
+	if (info) {
+		unregister_framebuffer(info);
+		if (info->cmap.len)
+			fb_dealloc_cmap(&info->cmap);
+		framebuffer_release(info);
+	}
+
+	if (fbh->fb)
+		fbh->fb->funcs->destroy(fbh->fb);
+
+	drm_fb_helper_fini(fbh);
+}
+
+static int armada_fb_probe(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	int ret = 0;
+
+	if (!fbh->fb) {
+		ret = armada_fb_create(fbh, sizes);
+		if (ret == 0)
+			ret = 1;
+	}
+	return ret;
+}
+
+static struct drm_fb_helper_funcs armada_fb_helper_funcs = {
+	.gamma_set	= armada_drm_crtc_gamma_set,
+	.gamma_get	= armada_drm_crtc_gamma_get,
+	.fb_probe	= armada_fb_probe,
+};
+
+int armada_fbdev_init(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh;
+	int ret;
+
+	fbh = kzalloc(sizeof(*fbh), GFP_KERNEL);
+	if (!fbh)
+		return -ENOMEM;
+
+	priv->fbdev = fbh;
+
+	fbh->funcs = &armada_fb_helper_funcs;
+
+	ret = drm_fb_helper_init(dev, fbh, 1, 1);
+	if (ret) {
+		DRM_ERROR("failed to initialize drm fb helper\n");
+		goto err_fb_helper;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(fbh);
+	if (ret) {
+		DRM_ERROR("failed to add fb connectors\n");
+		goto err_fb_setup;
+	}
+
+	ret = drm_fb_helper_initial_config(fbh, 32);
+	if (ret) {
+		DRM_ERROR("failed to set initial config\n");
+		goto err_fb_setup;
+	}
+
+	return 0;
+ err_fb_setup:
+	drm_fb_helper_fini(fbh);
+ err_fb_helper:
+	kfree(fbh);
+	priv->fbdev = NULL;
+	return ret;
+}
+
+void armada_fbdev_fini(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh) {
+		armada_fb_destroy(fbh);
+		kfree(fbh);
+		priv->fbdev = NULL;
+	}
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
new file mode 100644
index 0000000..c4e7d5f
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/shmem_fs.h>
+#include <drm/drmP.h>
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+static int armada_gem_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct armada_gem_object *obj = drm_to_armada_gem(vma->vm_private_data);
+	unsigned long addr = (unsigned long)vmf->virtual_address;
+	unsigned long pfn = obj->phys_addr >> PAGE_SHIFT;
+	int ret;
+
+	pfn += (addr - vma->vm_start) >> PAGE_SHIFT;
+	ret = vm_insert_pfn(vma, addr, pfn);
+
+	switch (ret) {
+	case -EIO:
+	case -EAGAIN:
+		set_need_resched();
+	case 0:
+	case -ERESTARTSYS:
+	case -EINTR:
+		return VM_FAULT_NOPAGE;
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	default:
+		return VM_FAULT_SIGBUS;
+	}
+}
+
+const struct vm_operations_struct armada_gem_vm_ops = {
+	.fault	= armada_gem_vm_fault,
+	.open	= drm_gem_vm_open,
+	.close	= drm_gem_vm_close,
+};
+
+static size_t roundup_gem_size(size_t size)
+{
+	return roundup(size, PAGE_SIZE);
+}
+
+void armada_gem_free_object(struct drm_gem_object *obj)
+{
+	struct armada_gem_object *dobj = drm_to_armada_gem(obj);
+
+	DRM_DEBUG_DRIVER("release obj %p\n", dobj);
+
+	if (dobj->linear)
+		drm_mm_put_block(dobj->linear);
+
+	if (dobj->obj.map_list.map)
+		drm_gem_free_mmap_offset(&dobj->obj);
+
+	if (dobj->page) {
+		unsigned int order = get_order(dobj->obj.size);
+		__free_pages(dobj->page, order);
+		/* avoid the iounmap below - these aren't ioremap'd */
+		dobj->addr = NULL;
+	}
+
+	if (dobj->addr)
+		iounmap(dobj->addr);
+
+	drm_gem_object_release(&dobj->obj);
+
+	kfree(dobj);
+}
+
+int
+armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
+{
+	struct armada_private *priv = dev->dev_private;
+	size_t size = obj->obj.size;
+
+	if (obj->page || obj->linear)
+		return 0;
+
+	/*
+	 * If it is a small allocation (typically cursor)
+	 * try to get it from the system
+	 */
+	if (size < 1048576) {
+		unsigned int order = get_order(size);
+		struct page *p = alloc_pages(GFP_KERNEL, order);
+
+		if (p) {
+			unsigned sz = (size + 31) & ~31;
+			uintptr_t ptr;
+
+			obj->phys_addr = page_to_phys(p);
+			obj->dev_addr = obj->phys_addr;
+			obj->page = p;
+			obj->addr = page_address(p);
+
+			memset(obj->addr, 0, PAGE_ALIGN(size));
+
+			/* FIXME: Hack around dirty cache */
+			ptr = (uintptr_t)obj->addr;
+			while (sz) {
+				asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
+				ptr += L1_CACHE_BYTES;
+				sz -= L1_CACHE_BYTES;
+			}
+			dsb();
+		}
+	}
+
+	/* Otherwise, grab it from our linear allocation */
+	if (!obj->page) {
+		struct drm_mm_node *free;
+		unsigned align = min_t(unsigned, size, SZ_2M);
+		void __iomem *ptr;
+
+		free = drm_mm_search_free(&priv->linear, size, align, 0);
+		if (free)
+			obj->linear = drm_mm_get_block(free, size, align);
+		if (!obj->linear)
+			return -ENOSPC;
+
+		/* Ensure that the memory we're returning is cleared. */
+		ptr = ioremap_wc(obj->linear->start, size);
+		if (!ptr) {
+			drm_mm_put_block(obj->linear);
+			obj->linear = NULL;
+			return -ENOMEM;
+		}
+
+		memset_io(ptr, 0, size);
+		iounmap(ptr);
+
+		obj->phys_addr = obj->linear->start;
+		obj->dev_addr = obj->linear->start;
+	}
+
+	DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n",
+			 obj, obj->phys_addr, obj->dev_addr);
+
+	return 0;
+}
+
+void *
+armada_gem_map_object(struct drm_device *dev, struct armada_gem_object *dobj)
+{
+	if (!dobj->addr)
+		dobj->addr = ioremap_wc(dobj->phys_addr, dobj->obj.size);
+	return dobj->addr;
+}
+
+struct armada_gem_object *
+armada_gem_alloc_private_object(struct drm_device *dev, size_t size)
+{
+	struct armada_gem_object *obj;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_private_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	DRM_DEBUG_DRIVER("alloc private obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+struct armada_gem_object *armada_gem_alloc_object(struct drm_device *dev,
+	size_t size)
+{
+	struct armada_gem_object *obj;
+	struct address_space *mapping;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	mapping = obj->obj.filp->f_path.dentry->d_inode->i_mapping;
+	mapping_set_gfp_mask(mapping, GFP_HIGHUSER | __GFP_RECLAIMABLE);
+
+	DRM_DEBUG_DRIVER("alloc obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+/* Dumb alloc support */
+int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
+	struct drm_mode_create_dumb *args)
+{
+	struct armada_gem_object *dobj;
+	u32 handle;
+	size_t size;
+	int ret;
+
+	args->pitch = armada_pitch(args->width, args->bpp);
+	args->size = size = args->pitch * args->height;
+
+	dobj = armada_gem_alloc_private_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = armada_gem_linear_back(dev, dobj);
+	if (ret)
+		goto err;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle, uint64_t *offset)
+{
+	struct armada_gem_object *obj;
+	int ret = 0;
+
+	mutex_lock(&dev->struct_mutex);
+	obj = armada_gem_object_lookup(dev, file, handle);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		ret = -EINVAL;
+		goto err_unlock;
+	}
+
+	if (!obj->obj.map_list.map)
+		ret = drm_gem_create_mmap_offset(&obj->obj);
+
+	if (ret == 0) {
+		*offset = (u64)obj->obj.map_list.hash.key << PAGE_SHIFT;
+		DRM_DEBUG_DRIVER("handle %#x offset %llx\n", handle, *offset);
+	}
+
+	drm_gem_object_unreference(&obj->obj);
+ err_unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle)
+{
+	return drm_gem_handle_delete(file, handle);
+}
+
+/* Private driver gem ioctls */
+int armada_gem_create_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create *args = data;
+	struct armada_gem_object *dobj;
+	size_t size;
+	u32 handle;
+	int ret;
+
+	if (args->size == 0)
+		return -ENOMEM;
+
+	size = args->size;
+
+	dobj = armada_gem_alloc_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_create_phys_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create_phys *arg = data;
+	struct armada_gem_object *dobj;
+	u32 handle;
+	int ret;
+
+	dobj = armada_gem_alloc_private_object(dev, arg->size);
+	if (dobj) {
+		dobj->phys_addr = arg->phys;
+		dobj->dev_addr = arg->phys;
+	}
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret) {
+		drm_gem_object_release(&dobj->obj);
+		return ret;
+	}
+
+	/* drop reference from allocate - handle holds it now */
+	drm_gem_object_unreference(&dobj->obj);
+
+	arg->handle = handle;
+
+	return 0;
+}
+
+/* Map a shmem-backed object into process memory space */
+int armada_gem_mmap_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_mmap *args = data;
+	struct armada_gem_object *dobj;
+	unsigned long addr;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	if (!dobj->obj.filp) {
+		drm_gem_object_unreference(&dobj->obj);
+		return -EINVAL;
+	}
+
+	addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE,
+	               MAP_SHARED, args->offset);
+	drm_gem_object_unreference(&dobj->obj);
+	if (IS_ERR_VALUE(addr))
+		return addr;
+
+	args->addr = addr;
+
+	return 0;
+}
+
+int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_prop *arg = data;
+	struct armada_gem_object *dobj;
+	int ret;
+
+	ret = mutex_lock_interruptible(&dev->struct_mutex);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, arg->handle);
+	if (dobj == NULL) {
+		ret = -ENOENT;
+		goto unlock;
+	}
+
+	arg->phys = dobj->phys_addr;
+	ret = 0;
+	drm_gem_object_unreference(&dobj->obj);
+
+ unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_pwrite *args = data;
+	struct armada_gem_object *dobj;
+	char __user *ptr;
+	int ret;
+
+	DRM_DEBUG_DRIVER("handle %u off %u size %u ptr 0x%llx\n",
+		args->handle, args->offset, args->size, args->ptr);
+
+	if (args->size == 0)
+		return 0;
+
+	ptr = (char __user *)(uintptr_t)args->ptr;
+
+	if (!access_ok(VERIFY_READ, ptr, args->size))
+		return -EFAULT;
+
+	ret = fault_in_multipages_readable(ptr, args->size);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	/* Must be a kernel-mapped object */
+	if (!dobj->addr)
+		return -EINVAL;
+
+	if (args->offset > dobj->obj.size ||
+	    args->size > dobj->obj.size - args->offset) {
+		DRM_ERROR("invalid size: object size %u\n", dobj->obj.size);
+		ret = -EINVAL;
+		goto unref;
+	}
+
+	if (copy_from_user(dobj->addr + args->offset, ptr, args->size)) {
+		ret = -EFAULT;
+	} else if (dobj->update) {
+		dobj->update(dobj->update_data);
+		ret = 0;
+	}
+
+  unref:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.h b/drivers/gpu/drm/armada/armada_gem.h
new file mode 100644
index 0000000..87cd94b5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_GEM_H
+#define ARMADA_GEM_H
+
+/* GEM */
+struct armada_gem_object {
+	struct drm_gem_object	obj;
+	void			*addr;
+	struct drm_mm_node	*linear;
+	phys_addr_t		phys_addr;
+	resource_size_t		dev_addr;
+	struct page		*page;
+	void			(*update)(void *);
+	void			*update_data;
+};
+
+#define drm_to_armada_gem(o) container_of(o, struct armada_gem_object, obj)
+
+void armada_gem_free_object(struct drm_gem_object *);
+int armada_gem_linear_back(struct drm_device *, struct armada_gem_object *);
+void *armada_gem_map_object(struct drm_device *, struct armada_gem_object *);
+struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *, size_t);
+int armada_gem_dumb_create(struct drm_file *, struct drm_device *,
+	struct drm_mode_create_dumb *);
+int armada_gem_dumb_map_offset(struct drm_file *, struct drm_device *,
+	uint32_t, uint64_t *);
+int armada_gem_dumb_destroy(struct drm_file *, struct drm_device *,
+	uint32_t);
+
+static inline struct armada_gem_object *armada_gem_object_lookup(
+	struct drm_device *dev, struct drm_file *dfile, unsigned handle)
+{
+	struct drm_gem_object *obj = drm_gem_object_lookup(dev, dfile, handle);
+
+	return obj ? drm_to_armada_gem(obj) : NULL;
+}
+#endif
diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h
new file mode 100644
index 0000000..58d2a20
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_hw.h
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_HW_H
+#define ARMADA_HW_H
+
+/*
+ * Note: the following registers are written from IRQ context:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *  LCD_SPU_DMA_START_ADDR_[YUV][01], LCD_SPU_DMA_PITCH_YC,
+ *  LCD_SPU_DMA_PITCH_UV, LCD_SPU_DMA_OVSA_HPXL_VLN,
+ *  LCD_SPU_DMA_HPXL_VLN, LCD_SPU_DZM_HPXL_VLN, LCD_SPU_DMA_CTRL0
+ */
+enum {
+	LCD_SPU_ADV_REG			= 0x0084,
+	LCD_SPU_DMA_START_ADDR_Y0	= 0x00c0,
+	LCD_SPU_DMA_START_ADDR_U0	= 0x00c4,
+	LCD_SPU_DMA_START_ADDR_V0	= 0x00c8,
+	LCD_CFG_DMA_START_ADDR_0	= 0x00cc,
+	LCD_SPU_DMA_START_ADDR_Y1	= 0x00d0,
+	LCD_SPU_DMA_START_ADDR_U1	= 0x00d4,
+	LCD_SPU_DMA_START_ADDR_V1	= 0x00d8,
+	LCD_CFG_DMA_START_ADDR_1	= 0x00dc,
+	LCD_SPU_DMA_PITCH_YC		= 0x00e0,
+	LCD_SPU_DMA_PITCH_UV		= 0x00e4,
+	LCD_SPU_DMA_OVSA_HPXL_VLN	= 0x00e8,
+	LCD_SPU_DMA_HPXL_VLN		= 0x00ec,
+	LCD_SPU_DZM_HPXL_VLN		= 0x00f0,
+	LCD_CFG_GRA_START_ADDR0		= 0x00f4,
+	LCD_CFG_GRA_START_ADDR1		= 0x00f8,
+	LCD_CFG_GRA_PITCH		= 0x00fc,
+	LCD_SPU_GRA_OVSA_HPXL_VLN	= 0x0100,
+	LCD_SPU_GRA_HPXL_VLN		= 0x0104,
+	LCD_SPU_GZM_HPXL_VLN		= 0x0108,
+	LCD_SPU_HWC_OVSA_HPXL_VLN	= 0x010c,
+	LCD_SPU_HWC_HPXL_VLN		= 0x0110,
+	LCD_SPUT_V_H_TOTAL		= 0x0114,
+	LCD_SPU_V_H_ACTIVE		= 0x0118,
+	LCD_SPU_H_PORCH			= 0x011c,
+	LCD_SPU_V_PORCH			= 0x0120,
+	LCD_SPU_BLANKCOLOR		= 0x0124,
+	LCD_SPU_ALPHA_COLOR1		= 0x0128,
+	LCD_SPU_ALPHA_COLOR2		= 0x012c,
+	LCD_SPU_COLORKEY_Y		= 0x0130,
+	LCD_SPU_COLORKEY_U		= 0x0134,
+	LCD_SPU_COLORKEY_V		= 0x0138,
+	LCD_CFG_RDREG4F			= 0x013c,
+	LCD_SPU_SPI_RXDATA		= 0x0140,
+	LCD_SPU_ISA_RSDATA		= 0x0144,
+	LCD_SPU_HWC_RDDAT		= 0x0158,
+	LCD_SPU_GAMMA_RDDAT		= 0x015c,
+	LCD_SPU_PALETTE_RDDAT		= 0x0160,
+	LCD_SPU_IOPAD_IN		= 0x0178,
+	LCD_CFG_RDREG5F			= 0x017c,
+	LCD_SPU_SPI_CTRL		= 0x0180,
+	LCD_SPU_SPI_TXDATA		= 0x0184,
+	LCD_SPU_SMPN_CTRL		= 0x0188,
+	LCD_SPU_DMA_CTRL0		= 0x0190,
+	LCD_SPU_DMA_CTRL1		= 0x0194,
+	LCD_SPU_SRAM_CTRL		= 0x0198,
+	LCD_SPU_SRAM_WRDAT		= 0x019c,
+	LCD_SPU_SRAM_PARA0		= 0x01a0,
+	LCD_SPU_SRAM_PARA1		= 0x01a4,
+	LCD_CFG_SCLK_DIV		= 0x01a8,
+	LCD_SPU_CONTRAST		= 0x01ac,
+	LCD_SPU_SATURATION		= 0x01b0,
+	LCD_SPU_CBSH_HUE		= 0x01b4,
+	LCD_SPU_DUMB_CTRL		= 0x01b8,
+	LCD_SPU_IOPAD_CONTROL		= 0x01bc,
+	LCD_SPU_IRQ_ENA			= 0x01c0,
+	LCD_SPU_IRQ_ISR			= 0x01c4,
+};
+
+/* For LCD_SPU_ADV_REG */
+enum {
+	ADV_VSYNC_L_OFF	= 0xfff << 20,
+	ADV_GRACOLORKEY	= 1 << 19,
+	ADV_VIDCOLORKEY	= 1 << 18,
+	ADV_HWC32BLEND	= 1 << 15,
+	ADV_HWC32ARGB	= 1 << 14,
+	ADV_HWC32ENABLE	= 1 << 13,
+	ADV_VSYNCOFFEN	= 1 << 12,
+	ADV_VSYNC_H_OFF	= 0xfff << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL0 */
+enum {
+	CFG_NOBLENDING	= 1 << 31,
+	CFG_GAMMA_ENA	= 1 << 30,
+	CFG_CBSH_ENA	= 1 << 29,
+	CFG_PALETTE_ENA	= 1 << 28,
+	CFG_ARBFAST_ENA	= 1 << 27,
+	CFG_HWC_1BITMOD	= 1 << 26,
+	CFG_HWC_1BITENA	= 1 << 25,
+	CFG_HWC_ENA	= 1 << 24,
+	CFG_DMAFORMAT	= 0xf << 20,
+	CFG_DMA_565	= 0x0 << 20,
+	CFG_DMA_1555	= 0x1 << 20,
+	CFG_DMA_888PACK	= 0x2 << 20,
+	CFG_DMA_X888	= 0x3 << 20,
+	CFG_DMA_8888	= 0x4 << 20,
+	CFG_DMA_422PACK	= 0x5 << 20,
+	CFG_DMA_422	= 0x6 << 20,
+	CFG_DMA_420	= 0x7 << 20,
+	CFG_DMA_PSEUDO4	= 0x9 << 20,
+	CFG_DMA_PSEUDO8	= 0xa << 20,
+	CFG_GRAFORMAT	= 0xf << 16,
+	CFG_GRA_565	= 0x0 << 16,
+	CFG_GRA_1555	= 0x1 << 16,
+	CFG_GRA_888PACK	= 0x2 << 16,
+	CFG_GRA_X888	= 0x3 << 16,
+	CFG_GRA_8888	= 0x4 << 16,
+	CFG_GRA_422PACK	= 0x5 << 16,
+	CFG_GRA_422	= 0x6 << 16,
+	CFG_GRA_420	= 0x7 << 16,
+	CFG_GRA_PSEUDO4	= 0x9 << 16,
+	CFG_GRA_PSEUDO8	= 0xa << 16,
+	CFG_GRA_FTOGGLE	= 1 << 15,
+	CFG_GRA_HSMOOTH	= 1 << 14,
+	CFG_GRA_TSTMODE	= 1 << 13,
+	CFG_GRA_SWAPRB	= 1 << 12,
+	CFG_GRA_SWAPUV	= 1 << 11,
+	CFG_GRA_SWAPYU	= 1 << 10,
+	CFG_YUV2RGB_GRA	= 1 << 9,
+	CFG_GRA_ENA	= 1 << 8,
+	CFG_DMA_FTOGGLE	= 1 << 7,
+	CFG_DMA_HSMOOTH	= 1 << 6,
+	CFG_DMA_TSTMODE	= 1 << 5,
+	CFG_DMA_SWAPRB	= 1 << 4,
+	CFG_DMA_SWAPUV	= 1 << 3,
+	CFG_DMA_SWAPYU	= 1 << 2,
+	CFG_YUV2RGB_DMA	= 1 << 1,
+	CFG_DMA_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL1 */
+enum {
+	CFG_FRAME_TRIG	= 1 << 31,
+	CFG_VSYNC_INV	= 1 << 27,
+	CFG_CKMODE_MASK	= 0x7 << 24,
+	CFG_CKMODE_DIS	= 0x0 << 24,
+	CFG_CKMODE_Y	= 0x1 << 24,
+	CFG_CKMODE_U	= 0x2 << 24,
+	CFG_CKMODE_RGB	= 0x3 << 24,
+	CFG_CKMODE_V	= 0x4 << 24,
+	CFG_CKMODE_R	= 0x5 << 24,
+	CFG_CKMODE_G	= 0x6 << 24,
+	CFG_CKMODE_B	= 0x7 << 24,
+	CFG_CARRY	= 1 << 23,
+	CFG_GATED_CLK	= 1 << 21,
+	CFG_PWRDN_ENA	= 1 << 20,
+	CFG_DSCALE_MASK	= 0x3 << 18,
+	CFG_DSCALE_NONE	= 0x0 << 18,
+	CFG_DSCALE_HALF	= 0x1 << 18,
+	CFG_DSCALE_QUAR = 0x2 << 18,
+	CFG_ALPHAM_MASK	= 0x3 << 16,
+	CFG_ALPHAM_VIDEO= 0x0 << 16,
+	CFG_ALPHAM_GRA	= 0x1 << 16,
+	CFG_ALPHAM_CFG	= 0x2 << 16,
+	CFG_ALPHA_MASK	= 0xff << 8,
+	CFG_PIXCMD_MASK	= 0xff,
+};
+
+/* For LCD_SPU_SRAM_CTRL */
+enum {
+	SRAM_READ	= 0 << 14,
+	SRAM_WRITE	= 2 << 14,
+	SRAM_INIT	= 3 << 14,
+	SRAM_HWC32_RAMR	= 0xc << 8,
+	SRAM_HWC32_RAMG	= 0xd << 8,
+	SRAM_HWC32_RAMB	= 0xe << 8,
+	SRAM_HWC32_TRAN	= 0xf << 8,
+	SRAM_HWC	= 0xf << 8,
+};
+
+/* For LCD_SPU_SRAM_PARA1 */
+enum {
+	CFG_CSB_256x32	= 1 << 15,
+	CFG_CSB_256x24	= 1 << 14,
+	CFG_CSB_256x8	= 1 << 13,
+	CFG_PDWN256x32	= 1 << 7,
+	CFG_PDWN256x24	= 1 << 6,
+	CFG_PDWN256x8	= 1 << 5,
+	CFG_PDWN32x32	= 1 << 3,
+	CFG_PDWN16x66	= 1 << 2,
+	CFG_PDWN32x66	= 1 << 1,
+	CFG_PDWN64x66	= 1 << 0,
+};
+
+/* For LCD_CFG_SCLK_DIV */
+enum {
+	SCLK_510_AXI		= 0x0 << 30,	/* Armada 510 */
+	SCLK_510_EXTCLK0	= 0x1 << 30,	/* Armada 510 */
+	SCLK_510_PLL		= 0x2 << 30,	/* Armada 510 */
+	SCLK_510_EXTCLK1	= 0x3 << 30,	/* Armada 510 */
+	SCLK_DIV_CHANGE		= 1 << 29,
+	SCLK_16X_AHB		= 0x0 << 28,	/* Armada 16x */
+	SCLK_16X_PCLK		= 0x1 << 28,	/* Armada 16x */
+	SCLK_16X_AXI		= 0x4 << 28,	/* Armada 16x */
+	SCLK_16X_PLL		= 0x8 << 28,	/* Armada 16x */
+};
+
+/* For LCD_SPU_DUMB_CTRL */
+enum {
+	DUMB16_RGB565_0	= 0x0 << 28,
+	DUMB16_RGB565_1	= 0x1 << 28,
+	DUMB18_RGB666_0	= 0x2 << 28,
+	DUMB18_RGB666_1	= 0x3 << 28,
+	DUMB12_RGB444_0	= 0x4 << 28,
+	DUMB12_RGB444_1	= 0x5 << 28,
+	DUMB24_RGB888_0	= 0x6 << 28,
+	DUMB_BLANK	= 0x7 << 28,
+	DUMB_MASK	= 0xf << 28,
+	CFG_BIAS_OUT	= 1 << 8,
+	CFG_REV_RGB	= 1 << 7,
+	CFG_INV_CBLANK	= 1 << 6,
+	CFG_INV_CSYNC	= 1 << 5,
+	CFG_INV_HENA	= 1 << 4,	/* Normally active high */
+	CFG_INV_VSYNC	= 1 << 3,	/* Normally active high */
+	CFG_INV_HSYNC	= 1 << 2,	/* Normally active high */
+	CFG_INV_PCLK	= 1 << 1,
+	CFG_DUMB_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_IOPAD_CONTROL */
+enum {
+	CFG_VSCALE_LN_EN	= 3 << 18,
+	CFG_GRA_VM_ENA		= 1 << 15,
+	CFG_DMA_VM_ENA		= 1 << 13,
+	CFG_CMD_VM_ENA		= 1 << 11,
+	CFG_CSC_MASK		= 3 << 8,
+	CFG_CSC_CCIR709		= 1 << 9,
+	CFG_CSC_PROF		= 1 << 8,
+	CFG_IOPAD_MASK		= 0xf << 0,
+	CFG_IOPAD_DUMB24	= 0x0 << 0,
+	CFG_IOPAD_DUMB18SPI	= 0x1 << 0,
+	CFG_IOPAD_DUMB18GPIO	= 0x2 << 0,
+	CFG_IOPAD_DUMB16SPI	= 0x3 << 0,
+	CFG_IOPAD_DUMB16GPIO	= 0x4 << 0,
+	CFG_IOPAD_DUMB12GPIO	= 0x5 << 0,
+	CFG_IOPAD_SMART18	= 0x6 << 0,
+	CFG_IOPAD_SMART16	= 0x7 << 0,
+	CFG_IOPAD_SMART8	= 0x8 << 0,
+};
+
+#define IOPAD_DUMB24                0x0
+
+/* For LCD_SPU_IRQ_ENA */
+enum {
+	DMA_FRAME_IRQ0_ENA	= 1 << 31,
+	DMA_FRAME_IRQ1_ENA	= 1 << 30,
+	DMA_FRAME_IRQ_ENA	= DMA_FRAME_IRQ0_ENA | DMA_FRAME_IRQ1_ENA,
+	DMA_FF_UNDERFLOW_ENA	= 1 << 29,
+	GRA_FRAME_IRQ0_ENA	= 1 << 27,
+	GRA_FRAME_IRQ1_ENA	= 1 << 26,
+	GRA_FRAME_IRQ_ENA	= GRA_FRAME_IRQ0_ENA | GRA_FRAME_IRQ1_ENA,
+	GRA_FF_UNDERFLOW_ENA	= 1 << 25,
+	VSYNC_IRQ_ENA		= 1 << 23,
+	DUMB_FRAMEDONE_ENA	= 1 << 22,
+	TWC_FRAMEDONE_ENA	= 1 << 21,
+	HWC_FRAMEDONE_ENA	= 1 << 20,
+	SLV_IRQ_ENA		= 1 << 19,
+	SPI_IRQ_ENA		= 1 << 18,
+	PWRDN_IRQ_ENA		= 1 << 17,
+	ERR_IRQ_ENA		= 1 << 16,
+	CLEAN_SPU_IRQ_ISR	= 0xffff,
+};
+
+/* For LCD_SPU_IRQ_ISR */
+enum {
+	DMA_FRAME_IRQ0		= 1 << 31,
+	DMA_FRAME_IRQ1		= 1 << 30,
+	DMA_FRAME_IRQ		= DMA_FRAME_IRQ0 | DMA_FRAME_IRQ1,
+	DMA_FF_UNDERFLOW	= 1 << 29,
+	GRA_FRAME_IRQ0		= 1 << 27,
+	GRA_FRAME_IRQ1		= 1 << 26,
+	GRA_FRAME_IRQ		= GRA_FRAME_IRQ0 | GRA_FRAME_IRQ1,
+	GRA_FF_UNDERFLOW	= 1 << 25,
+	VSYNC_IRQ		= 1 << 23,
+	DUMB_FRAMEDONE		= 1 << 22,
+	TWC_FRAMEDONE		= 1 << 21,
+	HWC_FRAMEDONE		= 1 << 20,
+	SLV_IRQ			= 1 << 19,
+	SPI_IRQ			= 1 << 18,
+	PWRDN_IRQ		= 1 << 17,
+	ERR_IRQ			= 1 << 16,
+	DMA_FRAME_IRQ0_LEVEL	= 1 << 15,
+	DMA_FRAME_IRQ1_LEVEL	= 1 << 14,
+	DMA_FRAME_CNT_ISR	= 3 << 12,
+	GRA_FRAME_IRQ0_LEVEL	= 1 << 11,
+	GRA_FRAME_IRQ1_LEVEL	= 1 << 10,
+	GRA_FRAME_CNT_ISR	= 3 << 8,
+	VSYNC_IRQ_LEVEL		= 1 << 7,
+	DUMB_FRAMEDONE_LEVEL	= 1 << 6,
+	TWC_FRAMEDONE_LEVEL	= 1 << 5,
+	HWC_FRAMEDONE_LEVEL	= 1 << 4,
+	SLV_FF_EMPTY		= 1 << 3,
+	DMA_FF_ALLEMPTY		= 1 << 2,
+	GRA_FF_ALLEMPTY		= 1 << 1,
+	PWRDN_IRQ_LEVEL		= 1 << 0,
+};
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctl.h b/drivers/gpu/drm/armada/armada_ioctl.h
new file mode 100644
index 0000000..c99cc5a
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctl.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  With inspiration from the i915 driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_ARMADA_IOCTL_H
+#define DRM_ARMADA_IOCTL_H
+
+#define DRM_ARMADA_GEM_CREATE		0x00
+#define DRM_ARMADA_GEM_CREATE_PHYS	0x01
+#define DRM_ARMADA_GEM_MMAP		0x02
+#define DRM_ARMADA_GEM_PWRITE		0x03
+#define DRM_ARMADA_GEM_PROP		0x04
+#define DRM_ARMADA_OVERLAY_PUT_IMAGE	0x06
+#define DRM_ARMADA_OVERLAY_ATTRS		0x07
+
+#define ARMADA_IOCTL(dir,name,str) \
+	DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
+
+struct drm_armada_gem_create {
+	uint32_t handle;
+	uint32_t size;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE \
+	ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
+
+struct drm_armada_gem_create_phys {
+	uint32_t size;
+	uint32_t handle;
+	uint64_t phys;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
+	ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
+
+struct drm_armada_gem_mmap {
+	uint32_t handle;
+	uint32_t pad;
+	uint64_t offset;
+	uint64_t size;
+	uint64_t addr;
+};
+#define DRM_IOCTL_ARMADA_GEM_MMAP \
+	ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
+
+struct drm_armada_gem_pwrite {
+	uint64_t ptr;
+	uint32_t handle;
+	uint32_t offset;
+	uint32_t size;
+};
+#define DRM_IOCTL_ARMADA_GEM_PWRITE \
+	ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
+
+struct drm_armada_gem_prop {
+	uint64_t phys;
+	uint32_t handle;
+};
+#define DRM_IOCTL_ARMADA_GEM_PROP \
+	ARMADA_IOCTL(IOWR, GEM_PROP, gem_prop)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_put_image {
+	uint32_t flags;
+#define ARMADA_OVERLAY_TYPE_MASK          0x000000ff
+#define ARMADA_OVERLAY_YUV_PLANAR         0x00000001
+#define ARMADA_OVERLAY_YUV_PACKED         0x00000002
+#define ARMADA_OVERLAY_RGB                0x00000003
+#define ARMADA_OVERLAY_DEPTH_MASK		0x0000ff00
+#define ARMADA_OVERLAY_RGB24		0x00001000
+#define ARMADA_OVERLAY_RGB16		0x00002000
+#define ARMADA_OVERLAY_RGB15		0x00003000
+#define ARMADA_OVERLAY_YUV422		0x00000100
+#define ARMADA_OVERLAY_YUV411		0x00000200
+#define ARMADA_OVERLAY_YUV420		0x00000300
+#define ARMADA_OVERLAY_YUV410		0x00000400
+#define ARMADA_OVERLAY_SWAP_MASK		0x00ff0000
+#define ARMADA_OVERLAY_NO_SWAP		0x00000000
+#define ARMADA_OVERLAY_UV_SWAP		0x00010000
+#define ARMADA_OVERLAY_Y_SWAP		0x00020000
+#define ARMADA_OVERLAY_Y_AND_UV_SWAP	0x00030000
+#define ARMADA_OVERLAY_FLAGS_MASK		0xff000000
+#define ARMADA_OVERLAY_ENABLE		0x01000000
+	uint32_t bo_handle;
+	uint16_t stride_Y;
+	uint16_t stride_UV;
+	uint32_t offset_Y;
+	uint32_t offset_U;
+	uint32_t offset_V;
+	uint16_t src_width;
+	uint16_t src_height;
+	uint16_t src_scan_width;
+	uint16_t src_scan_height;
+	uint32_t crtc_id;
+	uint16_t dst_x;
+	uint16_t dst_y;
+	uint16_t dst_width;
+	uint16_t dst_height;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_PUT_IMAGE \
+	ARMADA_IOCTL(IOW, OVERLAY_PUT_IMAGE, overlay_put_image)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_attrs {
+	uint32_t flags;
+#define ARMADA_OVERLAY_UPDATE_ATTRS	(1<<0)
+#define ARMADA_OVERLAY_UPDATE_GAMMA	(1<<1)
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+	uint32_t gamma0;
+	uint32_t gamma1;
+	uint32_t gamma2;
+	uint32_t gamma3;
+	uint32_t gamma4;
+	uint32_t gamma5;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_ATTRS \
+	ARMADA_IOCTL(IOWR, OVERLAY_ATTRS, overlay_attrs)
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctlP.h b/drivers/gpu/drm/armada/armada_ioctlP.h
new file mode 100644
index 0000000..8e6089e
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctlP.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_IOCTLP_H
+#define ARMADA_IOCTLP_H
+
+#define ARMADA_IOCTL_PROTO(name)\
+extern int armada_##name##_ioctl(struct drm_device *, void *, struct drm_file *)
+
+ARMADA_IOCTL_PROTO(gem_create);
+ARMADA_IOCTL_PROTO(gem_create_phys);
+ARMADA_IOCTL_PROTO(gem_mmap);
+ARMADA_IOCTL_PROTO(gem_prop);
+ARMADA_IOCTL_PROTO(gem_pwrite);
+ARMADA_IOCTL_PROTO(overlay_put_image);
+ARMADA_IOCTL_PROTO(overlay_attrs);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_output.c b/drivers/gpu/drm/armada/armada_output.c
new file mode 100644
index 0000000..3b95f0c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "drm_helper.h"
+#include "armada_output.h"
+#include "armada_drm.h"
+
+struct armada_connector {
+	struct drm_connector conn;
+	const struct armada_output_type *type;
+};
+
+#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = conn->encoder;
+
+	return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
+}
+
+static enum drm_connector_status armada_drm_connector_detect(
+	struct drm_connector *conn, bool force)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+	enum drm_connector_status status = connector_status_disconnected;
+
+	if (dconn->type->detect) {
+		status = dconn->type->detect(conn, force);
+	} else {
+		struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+
+		if (enc)
+			status = encoder_helper_funcs(enc)->detect(enc, conn);
+	}
+
+	return status;
+}
+
+static void armada_drm_connector_destroy(struct drm_connector *conn)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	drm_sysfs_connector_remove(conn);
+	drm_connector_cleanup(conn);
+	kfree(dconn);
+}
+
+static int armada_drm_connector_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	if (!dconn->type->set_property)
+		return -EINVAL;
+
+	return dconn->type->set_property(conn, property, value);
+}
+
+static const struct drm_connector_funcs armada_drm_conn_funcs = {
+	.dpms		= drm_helper_connector_dpms,
+	.fill_modes	= drm_helper_probe_single_connector_modes,
+	.detect		= armada_drm_connector_detect,
+	.destroy	= armada_drm_connector_destroy,
+	.set_property	= armada_drm_connector_set_property,
+};
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+void armada_drm_encoder_commit(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
+{
+	return true;
+}
+
+/* Shouldn't this be a generic helper function? */
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int valid = MODE_BAD;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		valid = slave->slave_funcs->mode_valid(encoder, mode);
+	}
+	return valid;
+}
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int rc = -EINVAL;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		rc = slave->slave_funcs->set_property(encoder, conn, property,
+						      value);
+	}
+	return rc;
+}
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data)
+{
+	struct armada_connector *dconn;
+	int ret;
+
+	dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
+	if (!dconn)
+		return -ENOMEM;
+
+	dconn->type = type;
+
+	ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
+				 type->connector_type);
+	if (ret) {
+		DRM_ERROR("unable to init connector\n");
+		goto err_destroy_dconn;
+	}
+
+	ret = type->create(&dconn->conn, data);
+	if (ret)
+		goto err_conn;
+
+	ret = drm_sysfs_connector_add(&dconn->conn);
+	if (ret)
+		goto err_sysfs;
+
+	return 0;
+
+ err_sysfs:
+	if (dconn->conn.encoder)
+		dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
+ err_conn:
+	drm_connector_cleanup(&dconn->conn);
+ err_destroy_dconn:
+	kfree(dconn);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_output.h b/drivers/gpu/drm/armada/armada_output.h
new file mode 100644
index 0000000..d655655
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CONNETOR_H
+#define ARMADA_CONNETOR_H
+
+#define encoder_helper_funcs(encoder) \
+	((struct drm_encoder_helper_funcs *)encoder->helper_private)
+
+struct armada_output_type {
+	int connector_type;
+	enum drm_connector_status (*detect)(struct drm_connector *, bool);
+	int (*create)(struct drm_connector *, const void *);
+	int (*set_property)(struct drm_connector *, struct drm_property *, uint64_t);
+};
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn);
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder);
+void armada_drm_encoder_commit(struct drm_encoder *encoder);
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj);
+
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode);
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value);
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c
new file mode 100644
index 0000000..68c4efd
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_overlay.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include "drm_helper.h"
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+struct armada_overlay {
+	struct armada_gem_object *obj;
+	struct armada_gem_object *old_obj;
+	struct armada_crtc *dcrtc;
+	uint32_t crtc_id;
+	uint32_t cached_flags;
+	uint32_t cached_cfg;
+
+	uint32_t stride_yc;
+	uint32_t stride_uv;
+	uint32_t dst_yx;
+	uint32_t src_hw;
+	uint32_t dst_hw;
+	uint32_t cfg;
+
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+
+	struct armada_vbl_event vbl_update;
+
+	struct armada_regs update[13];
+
+	wait_queue_head_t vbl_wait;
+};
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+static void armada_ovl_update(struct armada_crtc *dcrtc, void *data)
+{
+	struct armada_overlay *ovl = container_of(data, struct armada_overlay, update);
+	armada_drm_crtc_update_regs(dcrtc, data);
+	wake_up(&ovl->vbl_wait);
+}
+
+static void armada_ovl_update_attr(struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+	uint32_t key = ovl->color_key;
+        uint32_t r, g, b;
+
+        r = (key & 0x0000ff);
+        g = (key & 0x00ff00) >> 8;
+        b = (key & 0xff0000) >> 16;
+
+	writel_relaxed(r << 8 | r << 16 | r << 24, dcrtc->base + LCD_SPU_COLORKEY_Y);
+	writel_relaxed(g << 8 | g << 16 | g << 24, dcrtc->base + LCD_SPU_COLORKEY_U);
+	writel_relaxed(b << 8 | b << 16 | b << 24, dcrtc->base + LCD_SPU_COLORKEY_V);
+
+	writel_relaxed(0x00004000, dcrtc->base + LCD_SPU_CONTRAST);
+	writel_relaxed(0x40000000, dcrtc->base + LCD_SPU_SATURATION);
+	writel_relaxed(0x00002000, dcrtc->base + LCD_SPU_CBSH_HUE);
+
+	spin_lock_irq(&dcrtc->irq_lock);
+	armada_updatel(CFG_CKMODE_RGB | CFG_ALPHAM_GRA,
+		     CFG_CKMODE_MASK | CFG_ALPHAM_MASK | CFG_ALPHA_MASK,
+		     dcrtc->base + LCD_SPU_DMA_CTRL1);
+
+	armada_updatel(ADV_GRACOLORKEY, 0, dcrtc->base + LCD_SPU_ADV_REG);
+	spin_unlock_irq(&dcrtc->irq_lock);
+}
+
+static int armada_check_planar(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp;
+
+	DRM_DEBUG_DRIVER("stride Y%#x UV%#x offset Y%#x U%#x V%#x obj %#x\n",
+		args->stride_Y, args->stride_UV,
+		args->offset_Y, args->offset_U, args->offset_V,
+		obj->obj.size);
+
+	if (args->src_scan_width     > args->stride_Y ||
+	    args->src_scan_width / 2 > args->stride_UV)
+		return -EINVAL;
+
+	tmp = args->stride_Y * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	tmp = args->stride_UV * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_U > obj->obj.size - tmp ||
+	    args->offset_V > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int armada_check_packed(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp = args->stride_Y * args->src_height;
+
+	if (args->src_scan_width * 2 > args->stride_Y ||
+	    tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct armada_crtc *armada_crtc_lookup(struct drm_device *dev, uint32_t id)
+{
+	struct drm_crtc *crtc = drm_crtc_find(dev, id);
+	return crtc ? drm_to_armada_crtc(crtc) : NULL;
+}
+
+static void armada_ovl_release_old(struct armada_overlay *ovl)
+{
+	if (ovl->old_obj) {
+		drm_gem_object_unreference(&ovl->old_obj->obj);
+		ovl->old_obj = NULL;
+	}
+}
+
+/*
+ * This should be called with both dev->struct_mutex and
+ * the mode_config mutexes held.
+ */
+void armada_drm_overlay_off(struct drm_device *dev, struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+
+	ovl->cfg = 0;
+
+	if (dcrtc) {
+		/* Disable overlay */
+		armada_drm_vbl_event_remove_unlocked(dcrtc, &ovl->vbl_update);
+
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_updatel(0, CFG_DMA_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
+
+		ovl->dcrtc->overlay = NULL;
+		ovl->dcrtc = NULL;
+		ovl->crtc_id = ~0;
+	}
+
+	armada_ovl_release_old(ovl);
+
+	if (ovl->obj) {
+		drm_gem_object_unreference(&ovl->obj->obj);
+		ovl->obj = NULL;
+	}
+}
+
+static int armada_ovl_check_dst(const struct drm_armada_overlay_put_image *args,
+	const struct drm_display_mode *mode)
+{
+	if (args->dst_x < mode->hdisplay &&
+	    args->dst_width <= mode->hdisplay - args->dst_x &&
+	    args->dst_y < mode->vdisplay &&
+	    args->dst_height <= mode->vdisplay - args->dst_y)
+		return 0;
+	return -EINVAL;
+}
+
+static int armada_ovl_compute_cfg(struct armada_overlay *ovl, uint32_t flags)
+{
+	uint32_t cfg = CFG_DMA_HSMOOTH | CFG_CBSH_ENA;
+
+	switch (flags & ARMADA_OVERLAY_TYPE_MASK) {
+	case ARMADA_OVERLAY_YUV_PLANAR:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:	/* Planar YUV422 */
+			cfg |= CFG_DMA_422;
+			break;
+		case ARMADA_OVERLAY_YUV420:	/* Planar YUV420 */
+			cfg |= CFG_DMA_420;
+			break;
+		default:
+			DRM_ERROR("bad planar depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("planar and requested swap\n");
+			return -EINVAL;
+		}
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_YUV_PACKED:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:
+			cfg |= CFG_DMA_422PACK;
+			break;
+		default:
+			DRM_ERROR("bad packed depth\n");
+			return -EINVAL;
+		}
+		if (flags & (ARMADA_OVERLAY_SWAP_MASK & ~ARMADA_OVERLAY_Y_AND_UV_SWAP)) {
+			DRM_ERROR("bad overlay swap\n");
+			return -EINVAL;
+		}
+		/*
+		 *                            [0:7] [8:15] [16:23] [24:31]
+		 * 0:			YUY2:   Y     U       Y       V
+		 * UV_SWAP:		YVYU:   Y     V       Y       U
+		 * Y_SWAP:		UYVY:   U     Y       V       Y
+		 * Y_SWAP | UV_SWAP:	VYUY:   V     Y       U       Y
+		 *
+		 * Default ordering in memory:  U     Y0      V       Y1
+		 *
+		 * Image fourcc 59565955 UYVY flags 01020102 -> correct
+		 * Image fourcc 32595559 YUY2 flags 01000102 -> wrong U/V swapped
+		 */
+		if (flags & ARMADA_OVERLAY_UV_SWAP)
+			cfg |= CFG_DMA_SWAPUV;
+		if (!(flags & ARMADA_OVERLAY_Y_SWAP))
+			cfg ^= CFG_DMA_SWAPYU | CFG_DMA_SWAPUV;
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_RGB:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_RGB24:	/* 3 byte RGB */
+			cfg |= CFG_DMA_888PACK;
+			break;
+		case ARMADA_OVERLAY_RGB16:        /* 2 byte RGB */
+			cfg |= CFG_DMA_565;
+			break;
+		case ARMADA_OVERLAY_RGB15:        /* 2 byte RGB */
+			cfg |= CFG_DMA_1555;
+			break;
+		default:
+			DRM_ERROR("bad RGB depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("RGB and requested swap\n");
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		DRM_ERROR("bad overlay type\n");
+		return -EINVAL;
+	}
+
+	cfg |= CFG_DMA_ENA;
+
+	ovl->cached_flags = flags;
+	ovl->cached_cfg = cfg;
+	return 0;
+}
+
+int armada_overlay_put_image_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_put_image *args = data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+	struct armada_gem_object *obj;
+	struct armada_crtc *dcrtc;
+	uint32_t stride_uv, stride_yc, src_hw, dst_hw, dst_yx;
+	bool planar = false;
+	int ret, idx;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (!(args->flags & ARMADA_OVERLAY_ENABLE)) {
+		mutex_lock(&dev->mode_config.mutex);
+		mutex_lock(&dev->struct_mutex);
+
+		armada_drm_overlay_off(dev, ovl);
+
+		mutex_unlock(&dev->struct_mutex);
+		mutex_unlock(&dev->mode_config.mutex);
+
+		return 0;
+	}
+
+//	DRM_DEBUG_DRIVER("flags %x handle %x src %dx%d dst %dx%d+%d+%d\n",
+//		args->flags, args->bo_handle, args->src_scan_width, args->src_scan_height,
+//		args->dst_width, args->dst_height, args->dst_x, args->dst_y);
+
+	if (!ovl->dcrtc || ovl->crtc_id != args->crtc_id) {
+		dcrtc = armada_crtc_lookup(dev, args->crtc_id);
+		if (!dcrtc)
+			return -ENOENT;
+	} else {
+		dcrtc = ovl->dcrtc;
+	}
+
+	if (args->flags != ovl->cached_flags) {
+		ret = armada_ovl_compute_cfg(ovl, args->flags);
+		if (ret)
+			return ret;
+	}
+
+	obj = armada_gem_object_lookup(dev, file, args->bo_handle);
+	if (!obj)
+		return -ENOENT;
+
+	ret = wait_event_timeout(ovl->vbl_wait, list_empty(&ovl->vbl_update.node), HZ/25);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&dev->mode_config.mutex);
+	mutex_lock(&dev->struct_mutex);
+
+	/* Prevent updates */
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	if (ovl->dcrtc != dcrtc) {
+		armada_drm_overlay_off(dev, ovl);
+
+		ovl->crtc_id = args->crtc_id;
+		ovl->dcrtc = dcrtc;
+		dcrtc->overlay = ovl;
+
+		armada_ovl_update_attr(ovl);
+	}
+
+	ret = armada_ovl_check_dst(args, &ovl->dcrtc->crtc.mode);
+	if (ret)
+		goto err_unref;
+
+	planar = (args->flags & ARMADA_OVERLAY_TYPE_MASK) == ARMADA_OVERLAY_YUV_PLANAR;
+	if (planar)
+		ret = armada_check_planar(args, obj);
+	else
+		ret = armada_check_packed(args, obj);
+	if (ret)
+		goto err_unref;
+
+	if (ovl->dcrtc->interlaced) {
+		args->dst_y /= 2;
+		args->dst_height /= 2;
+	}
+
+	idx = 0;
+	if (ovl->obj != obj) {
+		uint32_t start_y, start_u, start_v;
+
+		/* switch to new object */
+		armada_ovl_release_old(ovl);
+		ovl->old_obj = ovl->obj;
+		ovl->obj = obj;
+
+		start_y = obj->dev_addr + args->offset_Y;
+		if (planar) {
+			start_u = obj->dev_addr + args->offset_U;
+			start_v = obj->dev_addr + args->offset_V;
+		} else {
+			start_u = start_y;
+			start_v = start_y;
+		}
+
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y0);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U0);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V0);
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y1);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U1);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V1);
+	} else {
+		drm_gem_object_unreference(&obj->obj);
+	}
+
+	stride_yc = args->stride_Y << 16 | args->stride_Y;
+	stride_uv = args->stride_UV << 16 | args->stride_UV;
+
+	if (ovl->stride_yc != stride_yc || ovl->stride_uv != stride_uv) {
+		ovl->stride_yc = stride_yc;
+		ovl->stride_uv = stride_uv;
+		armada_reg_queue_set(ovl->update, idx, stride_yc, LCD_SPU_DMA_PITCH_YC);
+		armada_reg_queue_set(ovl->update, idx, stride_uv, LCD_SPU_DMA_PITCH_UV);
+	}
+
+	src_hw = args->src_scan_height << 16 | args->src_scan_width;
+	dst_hw = args->dst_height << 16 | args->dst_width;
+	if (ovl->src_hw != src_hw || ovl->dst_hw != dst_hw) {
+		ovl->src_hw = src_hw;
+		ovl->dst_hw = dst_hw;
+
+		armada_reg_queue_set(ovl->update, idx, dst_hw, LCD_SPU_DZM_HPXL_VLN);
+		armada_reg_queue_set(ovl->update, idx, src_hw, LCD_SPU_DMA_HPXL_VLN);
+	}
+
+	dst_yx = args->dst_y << 16 | args->dst_x;
+	if (ovl->dst_yx != dst_yx) {
+		ovl->dst_yx = dst_yx;
+		armada_reg_queue_set(ovl->update, idx, dst_yx, LCD_SPU_DMA_OVSA_HPXL_VLN);
+	}
+
+	/* Update overlay DMA settings */
+	if (ovl->cfg != ovl->cached_cfg) {
+		ovl->cfg = ovl->cached_cfg;
+		armada_reg_queue_mod(ovl->update, idx, ovl->cached_cfg,
+			CFG_DMAFORMAT | CFG_DMA_FTOGGLE | CFG_DMA_HSMOOTH |
+			CFG_DMA_TSTMODE | CFG_DMA_SWAPRB | CFG_DMA_SWAPUV |
+			CFG_DMA_SWAPYU | CFG_YUV2RGB_DMA | CFG_DMA_ENA,
+			LCD_SPU_DMA_CTRL0);
+	}
+	if (idx) {
+		armada_reg_queue_end(ovl->update, idx);
+		armada_drm_vbl_event_add(dcrtc, &ovl->vbl_update);
+	}
+
+ err_unref:
+	if (ret)
+		drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+	mutex_unlock(&dev->mode_config.mutex);
+
+	return ret;
+}
+
+int armada_overlay_attrs_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_attrs *args = data;
+        struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_ATTRS) {
+		if (args->brightness < -128 || args->brightness > 127 ||
+		    args->contrast > 255 || args->saturation > 1023)
+			return -EINVAL;
+
+		ovl->color_key = args->color_key;
+		ovl->brightness = args->brightness;
+		ovl->contrast = args->contrast;
+		ovl->saturation = args->saturation;
+
+		mutex_lock(&dev->mode_config.mutex);
+		if (ovl->dcrtc)
+			armada_ovl_update_attr(ovl);
+		mutex_unlock(&dev->mode_config.mutex);
+	} else {
+		args->color_key = ovl->color_key;
+		args->brightness = ovl->brightness;
+		args->contrast = ovl->contrast;
+		args->saturation = ovl->saturation;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_GAMMA) {
+		/* args->gamma0..5 */
+	}
+
+	return 0;
+}
+
+int armada_overlay_create(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl;
+
+	ovl = kzalloc(sizeof(*ovl), GFP_KERNEL);
+	if (!ovl)
+		return -ENOMEM;
+
+	priv->overlay = ovl;
+
+	init_waitqueue_head(&ovl->vbl_wait);
+	armada_drm_vbl_event_init(&ovl->vbl_update, armada_ovl_update, ovl->update);
+
+	ovl->color_key = 0x0101fe;
+	ovl->brightness = -19;
+	ovl->contrast = 75;
+	ovl->saturation = 146;
+
+	return 0;
+}
+
+void armada_overlay_destroy(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	kfree(ovl);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.c b/drivers/gpu/drm/armada/armada_slave.c
new file mode 100644
index 0000000..422a345
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "armada_drm.h"
+#include "armada_output.h"
+#include "armada_slave.h"
+
+static int armada_drm_slave_get_modes(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+	int count = 0;
+
+	if (enc) {
+		struct drm_encoder_slave *slave = to_encoder_slave(enc);
+
+		count = slave->slave_funcs->get_modes(enc, conn);
+	}
+
+	return count;
+}
+
+static void armada_drm_slave_destroy(struct drm_encoder *enc)
+{
+	struct drm_encoder_slave *slave = to_encoder_slave(enc);
+	struct i2c_client *client = drm_i2c_encoder_get_client(enc);
+
+	if (slave->slave_funcs)
+		slave->slave_funcs->destroy(enc);
+	if (client)
+		i2c_put_adapter(client->adapter);
+
+	drm_encoder_cleanup(&slave->base);
+	kfree(slave);
+}
+
+static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
+	.destroy	= armada_drm_slave_destroy,
+};
+
+static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
+	.get_modes	= armada_drm_slave_get_modes,
+	.mode_valid	= armada_drm_slave_encoder_mode_valid,
+	.best_encoder	= armada_drm_connector_encoder,
+};
+
+static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
+	.dpms = drm_i2c_encoder_dpms,
+	.save = drm_i2c_encoder_save,
+	.restore = drm_i2c_encoder_restore,
+	.mode_fixup = drm_i2c_encoder_mode_fixup,
+	.prepare = drm_i2c_encoder_prepare,
+	.commit = drm_i2c_encoder_commit,
+	.mode_set = drm_i2c_encoder_mode_set,
+	.detect = drm_i2c_encoder_detect,
+};
+
+static int armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
+{
+	const struct armada_drm_slave_config *config = data;
+	struct drm_encoder_slave *slave;
+	struct i2c_adapter *adap;
+	int ret;
+
+	conn->interlace_allowed = config->interlace_allowed;
+	conn->doublescan_allowed = config->doublescan_allowed;
+	conn->polled = config->polled;
+
+	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
+
+	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+	if (!slave)
+		return -ENOMEM;
+
+	slave->base.possible_crtcs = config->crtcs;
+
+	adap = i2c_get_adapter(config->i2c_adapter_id);
+	if (!adap) {
+		kfree(slave);
+		return -EPROBE_DEFER;
+	}
+
+	ret = drm_encoder_init(conn->dev, &slave->base,
+			       &armada_drm_slave_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret) {
+		DRM_ERROR("unable to init encoder\n");
+		i2c_put_adapter(adap);
+		kfree(slave);
+		return ret;
+	}
+
+	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
+	i2c_put_adapter(adap);
+	if (ret) {
+		DRM_ERROR("unable to init encoder slave\n");
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
+
+	ret = slave->slave_funcs->create_resources(&slave->base, conn);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	conn->encoder = &slave->base;
+
+	return ret;
+}
+
+static const struct armada_output_type armada_drm_conn_slave = {
+	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
+	.create		= armada_drm_conn_slave_create,
+	.set_property	= armada_drm_slave_encoder_set_property,
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *config)
+{
+	return armada_output_create(dev, &armada_drm_conn_slave, config);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
new file mode 100644
index 0000000..bf2374c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_SLAVE_H
+#define ARMADA_SLAVE_H
+
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+
+struct armada_drm_slave_config {
+	int i2c_adapter_id;
+	uint32_t crtcs;
+	uint8_t polled;
+	bool interlace_allowed;
+	bool doublescan_allowed;
+	struct i2c_board_info info;
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/drm_helper.h b/drivers/gpu/drm/armada/drm_helper.h
new file mode 100644
index 0000000..d9f2e8d
--- /dev/null
+++ b/drivers/gpu/drm/armada/drm_helper.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_HELPER_H
+#define DRM_HELPER_H
+
+/* Useful helpers I wish the DRM core would provide */
+
+#include <drm/drmP.h>
+
+static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
+	return mo ? obj_to_crtc(mo) : NULL;
+}
+
+static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
+	return mo ? obj_to_encoder(mo) : NULL;
+}
+
+#endif
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 21:15               ` Russell King - ARM Linux
@ 2013-06-10 22:49                 ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 22:49 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 5:15 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 05:01:18PM -0400, Rob Clark wrote:
>> I would like to get at least some of the driver upstream.  I'd hate
>> for it to have to live completely out of tree.  I can think of a
>> couple possibilities.
>>
>> 1) the best would be, if there was some way for the drm driver to know
>> the upper/lower bounds of the carveout, then it could bounds-check
>> against this.  And then in worst case, userspace can just screw up
>> other "graphics" buffers
>
> The bounds check does what?  Check that the buffer object's physical
> address is within another driver's range?  Fine, but all that it's
> doing is preventing it being associated with a buffer object which can
> _only_ be used for _read_ access by the LCD controller.  There is no
> write access to the GEM objects by anything that this DRM driver talks
> to.

I was assuming there was a global carveout pool, so it would be just
one upper/lower check.  But from what you said and looking at the
allocation code in your driver, I guess this is not actually the case.

> So all it'll do is prevent you passing rogue addresses to the LCD
> controller for scanout.
>
> And how do we get that information into the driver from other random
> drivers?  Hack in random inter-driver specific APIs to do it?  Come
> up with yet another different way to try and identify whether a
> particular resource is a block of reserved memory for this driver's
> usage as a linear region or one of the others?  How.  Really, please
> think about the problem.
>
> The benefit vs the complexity involved really isn't worth it.

yeah, that suggestion was based on a wrong assumption about how the
phys contig buffers are allocated and passed.  So disregard it.

I guess in the short term, the best I can think is keep those phys
ioctls as a small patch on top of the upstream driver.  It is ok to
leave place-holder ioctl #'s in the upstream driver so that the ioctl
#'s don't shift when you rebase.  And then try to get the vendor to
add support for dmabuf so that the patch on top of upstream can
eventually be dropped.  Maybe someone else has a better suggestion,
but I don't think we can merge those phys ioctls upstream, and I'd
really hate to 'throw the baby out with the bathwater' in this case
and not at least get the modesetting part of the driver merged.

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 22:49                 ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 22:49 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 5:15 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 05:01:18PM -0400, Rob Clark wrote:
>> I would like to get at least some of the driver upstream.  I'd hate
>> for it to have to live completely out of tree.  I can think of a
>> couple possibilities.
>>
>> 1) the best would be, if there was some way for the drm driver to know
>> the upper/lower bounds of the carveout, then it could bounds-check
>> against this.  And then in worst case, userspace can just screw up
>> other "graphics" buffers
>
> The bounds check does what?  Check that the buffer object's physical
> address is within another driver's range?  Fine, but all that it's
> doing is preventing it being associated with a buffer object which can
> _only_ be used for _read_ access by the LCD controller.  There is no
> write access to the GEM objects by anything that this DRM driver talks
> to.

I was assuming there was a global carveout pool, so it would be just
one upper/lower check.  But from what you said and looking at the
allocation code in your driver, I guess this is not actually the case.

> So all it'll do is prevent you passing rogue addresses to the LCD
> controller for scanout.
>
> And how do we get that information into the driver from other random
> drivers?  Hack in random inter-driver specific APIs to do it?  Come
> up with yet another different way to try and identify whether a
> particular resource is a block of reserved memory for this driver's
> usage as a linear region or one of the others?  How.  Really, please
> think about the problem.
>
> The benefit vs the complexity involved really isn't worth it.

yeah, that suggestion was based on a wrong assumption about how the
phys contig buffers are allocated and passed.  So disregard it.

I guess in the short term, the best I can think is keep those phys
ioctls as a small patch on top of the upstream driver.  It is ok to
leave place-holder ioctl #'s in the upstream driver so that the ioctl
#'s don't shift when you rebase.  And then try to get the vendor to
add support for dmabuf so that the patch on top of upstream can
eventually be dropped.  Maybe someone else has a better suggestion,
but I don't think we can merge those phys ioctls upstream, and I'd
really hate to 'throw the baby out with the bathwater' in this case
and not at least get the modesetting part of the driver merged.

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 2/4] DRM: Armada: Add support for hardware cursors
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-10 22:49   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:49 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds hardware cursor support to the DRM driver for the
Marvell Armada SoCs.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/Kconfig       |    7 +
 drivers/gpu/drm/armada/armada_crtc.c |  201 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/armada/armada_crtc.h |    8 ++
 3 files changed, 216 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
index c7a0a94..6f64642 100644
--- a/drivers/gpu/drm/armada/Kconfig
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -13,3 +13,10 @@ config DRM_ARMADA
 	  This driver provides no built-in acceleration; acceleration is
 	  performed by other IP found on the SoC.  This driver provides
 	  kernel mode setting and buffer management to userspace.
+
+config DRM_ARMADA_CURSOR
+	bool "Enable hardware cursor support for Marvell Armada DRM"
+	depends on DRM_ARMADA != n
+	help
+	  Add support for hardware cursor support on the Marvell
+	  Armada devices.
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index dadaf63..4a71ba0 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -626,11 +626,210 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
 	.disable	= armada_drm_crtc_disable,
 };
 
+#ifdef CONFIG_DRM_ARMADA_CURSOR
+static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
+{
+	uint32_t xoff, xscr, w = dcrtc->cursor_w, s;
+	uint32_t yoff, yscr, h = dcrtc->cursor_h;
+
+	/*
+	 * Calculate the visible width and height of the cursor,
+	 * screen position, and the position in the cursor bitmap.
+	 */
+	if (dcrtc->cursor_x < 0) {
+		xoff = -dcrtc->cursor_x;
+		xscr = 0;
+		w -= min(xoff, w);
+	} else if (dcrtc->cursor_x + w > dcrtc->crtc.mode.hdisplay) {
+		xoff = 0;
+		xscr = dcrtc->cursor_x;
+		w = max_t(int, dcrtc->crtc.mode.hdisplay - dcrtc->cursor_x, 0);
+	} else {
+		xoff = 0;
+		xscr = dcrtc->cursor_x;
+	}
+
+	if (dcrtc->cursor_y < 0) {
+		yoff = -dcrtc->cursor_y;
+		yscr = 0;
+		h -= min(yoff, h);
+	} else if (dcrtc->cursor_y + h > dcrtc->crtc.mode.vdisplay) {
+		yoff = 0;
+		yscr = dcrtc->cursor_y;
+		h = max_t(int, dcrtc->crtc.mode.vdisplay - dcrtc->cursor_y, 0);
+	} else {
+		yoff = 0;
+		yscr = dcrtc->cursor_y;
+	}
+
+	/* On interlaced modes, the vertical cursor size must be halved */
+	s = dcrtc->cursor_w;
+	if (dcrtc->interlaced) {
+		s *= 2;
+		yscr /= 2;
+		h /= 2;
+	}
+
+	if (!dcrtc->cursor_obj || !h || !w) {
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
+		return 0;
+	}
+
+	armada_updatel(CFG_CSB_256x32, CFG_PDWN256x32,
+		     dcrtc->base + LCD_SPU_SRAM_PARA1);
+
+	if (dcrtc->cursor_lw != w || dcrtc->cursor_lh != h || reload) {
+		struct armada_gem_object *obj = dcrtc->cursor_obj;
+		uint32_t *pix, *p, col2 = 0, col3 = 0;
+		unsigned x, y, d, n, a;
+
+		dcrtc->cursor_lw = w;
+		dcrtc->cursor_lh = h;
+
+		pix = obj->addr;
+
+		/* Set the top-left corner of the cursor image */
+		pix += yoff * s + xoff;
+
+		a = 2 << 14 | 15 << 8;
+		for (d = n = y = 0; y < h; y++) {
+			for (x = 0, p = &pix[y * s]; x < w; x++, p++) {
+				uint32_t v = *p;
+				unsigned b;
+
+				if ((v & 0xff000000) != 0xff000000) {
+					b = 0;	/* transparent */
+				} else if (col2 == v) {
+					b = 2;	/* color 2 */
+				} else if (col3 == v) {
+					b = 3;	/* color 3 */
+				} else if (col2 == 0) {
+					col2 = v;
+					b = 2;	/* alloc color 2 */
+				} else if (col3 == 0) {
+					col3 = v;
+					b = 3;	/* alloc color 3 */
+				} else {
+					/* fail */
+					b = 1;	/* inverse (!) */
+				}
+
+				d |= b << n;
+				n += 2;
+
+				if (n == 32) {
+					writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
+					writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
+					a++;
+					d = n = 0;
+				}
+			}
+		}
+
+		if (n) {
+			writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
+			writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
+		}
+
+		writel_relaxed(col2, dcrtc->base + LCD_SPU_ALPHA_COLOR1);
+		writel_relaxed(col3, dcrtc->base + LCD_SPU_ALPHA_COLOR2);
+		writel_relaxed(h << 16 | w, dcrtc->base + LCD_SPU_HWC_HPXL_VLN);
+	}
+
+	writel_relaxed(yscr << 16 | xscr, dcrtc->base + LCD_SPU_HWC_OVSA_HPXL_VLN);
+
+	spin_lock_irq(&dcrtc->irq_lock);
+	armada_updatel(CFG_HWC_ENA,
+		     CFG_HWC_ENA | CFG_HWC_1BITMOD | CFG_HWC_1BITENA,
+		     dcrtc->base + LCD_SPU_DMA_CTRL0);
+	spin_unlock_irq(&dcrtc->irq_lock);
+
+	return 0;
+}
+
+static void cursor_update(void *data)
+{
+	armada_drm_crtc_cursor_update(data, true);
+}
+
+static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
+	struct drm_file *file, uint32_t handle, uint32_t w, uint32_t h)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_gem_object *obj = NULL;
+	int ret;
+
+	if (handle && w > 0 && h > 0) {
+		/* maximum size is 64x64 */
+		if (w > 64 || h > 64)
+			return -ENOMEM;
+
+		obj = armada_gem_object_lookup(dev, file, handle);
+		if (!obj)
+			return -ENOENT;
+
+		/* Must be a kernel-mapped object */
+		if (!obj->addr) {
+			drm_gem_object_unreference_unlocked(&obj->obj);
+			return -EINVAL;
+		}
+
+		if (obj->obj.size < w * h * 4) {
+			DRM_ERROR("buffer is too small\n");
+			drm_gem_object_unreference_unlocked(&obj->obj);
+			return -ENOMEM;
+		}
+	}
+
+	mutex_lock(&dev->struct_mutex);
+	if (dcrtc->cursor_obj) {
+		dcrtc->cursor_obj->update = NULL;
+		dcrtc->cursor_obj->update_data = NULL;
+		drm_gem_object_unreference(&dcrtc->cursor_obj->obj);
+	}
+	dcrtc->cursor_obj = obj;
+	dcrtc->cursor_w = w;
+	dcrtc->cursor_h = h;
+	ret = armada_drm_crtc_cursor_update(dcrtc, true);
+	if (obj) {
+		obj->update_data = dcrtc;
+		obj->update = cursor_update;
+	}
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+static int armada_drm_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	int ret;
+
+	mutex_lock(&dev->struct_mutex);
+	dcrtc->cursor_x = x;
+	dcrtc->cursor_y = y;
+	ret = armada_drm_crtc_cursor_update(dcrtc, false);
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+#else
+#define armada_drm_crtc_cursor_set NULL
+#define armada_drm_crtc_cursor_move NULL
+#endif
+
 static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
 {
 	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
 	struct armada_private *priv = crtc->dev->dev_private;
 
+	if (dcrtc->cursor_obj)
+		drm_gem_object_unreference(&dcrtc->cursor_obj->obj);
+
 	priv->dcrtc[dcrtc->num] = NULL;
 	drm_crtc_cleanup(&dcrtc->crtc);
 
@@ -708,6 +907,8 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
 }
 
 static struct drm_crtc_funcs armada_crtc_funcs = {
+	.cursor_set	= armada_drm_crtc_cursor_set,
+	.cursor_move	= armada_drm_crtc_cursor_move,
 	.destroy	= armada_drm_crtc_destroy,
 	.page_flip	= armada_drm_crtc_page_flip,
 	.set_config	= drm_crtc_helper_set_config,
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
index e00dcf5..16e9d93 100644
--- a/drivers/gpu/drm/armada/armada_crtc.h
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -48,6 +48,14 @@ struct armada_crtc {
 
 	struct armada_overlay	*overlay;
 
+	struct armada_gem_object	*cursor_obj;
+	int			cursor_x;
+	int			cursor_y;
+	uint32_t		cursor_w;
+	uint32_t		cursor_h;
+	uint32_t		cursor_lw;
+	uint32_t		cursor_lh;
+
 	int			dpms;
 	uint32_t		cfg_dma_ctrl0;
 	uint32_t		cfg_dumb_ctrl;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 2/4] DRM: Armada: Add support for hardware cursors
@ 2013-06-10 22:49   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:49 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

This patch adds hardware cursor support to the DRM driver for the
Marvell Armada SoCs.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/Kconfig       |    7 +
 drivers/gpu/drm/armada/armada_crtc.c |  201 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/armada/armada_crtc.h |    8 ++
 3 files changed, 216 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
index c7a0a94..6f64642 100644
--- a/drivers/gpu/drm/armada/Kconfig
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -13,3 +13,10 @@ config DRM_ARMADA
 	  This driver provides no built-in acceleration; acceleration is
 	  performed by other IP found on the SoC.  This driver provides
 	  kernel mode setting and buffer management to userspace.
+
+config DRM_ARMADA_CURSOR
+	bool "Enable hardware cursor support for Marvell Armada DRM"
+	depends on DRM_ARMADA != n
+	help
+	  Add support for hardware cursor support on the Marvell
+	  Armada devices.
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index dadaf63..4a71ba0 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -626,11 +626,210 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
 	.disable	= armada_drm_crtc_disable,
 };
 
+#ifdef CONFIG_DRM_ARMADA_CURSOR
+static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
+{
+	uint32_t xoff, xscr, w = dcrtc->cursor_w, s;
+	uint32_t yoff, yscr, h = dcrtc->cursor_h;
+
+	/*
+	 * Calculate the visible width and height of the cursor,
+	 * screen position, and the position in the cursor bitmap.
+	 */
+	if (dcrtc->cursor_x < 0) {
+		xoff = -dcrtc->cursor_x;
+		xscr = 0;
+		w -= min(xoff, w);
+	} else if (dcrtc->cursor_x + w > dcrtc->crtc.mode.hdisplay) {
+		xoff = 0;
+		xscr = dcrtc->cursor_x;
+		w = max_t(int, dcrtc->crtc.mode.hdisplay - dcrtc->cursor_x, 0);
+	} else {
+		xoff = 0;
+		xscr = dcrtc->cursor_x;
+	}
+
+	if (dcrtc->cursor_y < 0) {
+		yoff = -dcrtc->cursor_y;
+		yscr = 0;
+		h -= min(yoff, h);
+	} else if (dcrtc->cursor_y + h > dcrtc->crtc.mode.vdisplay) {
+		yoff = 0;
+		yscr = dcrtc->cursor_y;
+		h = max_t(int, dcrtc->crtc.mode.vdisplay - dcrtc->cursor_y, 0);
+	} else {
+		yoff = 0;
+		yscr = dcrtc->cursor_y;
+	}
+
+	/* On interlaced modes, the vertical cursor size must be halved */
+	s = dcrtc->cursor_w;
+	if (dcrtc->interlaced) {
+		s *= 2;
+		yscr /= 2;
+		h /= 2;
+	}
+
+	if (!dcrtc->cursor_obj || !h || !w) {
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
+		return 0;
+	}
+
+	armada_updatel(CFG_CSB_256x32, CFG_PDWN256x32,
+		     dcrtc->base + LCD_SPU_SRAM_PARA1);
+
+	if (dcrtc->cursor_lw != w || dcrtc->cursor_lh != h || reload) {
+		struct armada_gem_object *obj = dcrtc->cursor_obj;
+		uint32_t *pix, *p, col2 = 0, col3 = 0;
+		unsigned x, y, d, n, a;
+
+		dcrtc->cursor_lw = w;
+		dcrtc->cursor_lh = h;
+
+		pix = obj->addr;
+
+		/* Set the top-left corner of the cursor image */
+		pix += yoff * s + xoff;
+
+		a = 2 << 14 | 15 << 8;
+		for (d = n = y = 0; y < h; y++) {
+			for (x = 0, p = &pix[y * s]; x < w; x++, p++) {
+				uint32_t v = *p;
+				unsigned b;
+
+				if ((v & 0xff000000) != 0xff000000) {
+					b = 0;	/* transparent */
+				} else if (col2 == v) {
+					b = 2;	/* color 2 */
+				} else if (col3 == v) {
+					b = 3;	/* color 3 */
+				} else if (col2 == 0) {
+					col2 = v;
+					b = 2;	/* alloc color 2 */
+				} else if (col3 == 0) {
+					col3 = v;
+					b = 3;	/* alloc color 3 */
+				} else {
+					/* fail */
+					b = 1;	/* inverse (!) */
+				}
+
+				d |= b << n;
+				n += 2;
+
+				if (n == 32) {
+					writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
+					writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
+					a++;
+					d = n = 0;
+				}
+			}
+		}
+
+		if (n) {
+			writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
+			writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
+		}
+
+		writel_relaxed(col2, dcrtc->base + LCD_SPU_ALPHA_COLOR1);
+		writel_relaxed(col3, dcrtc->base + LCD_SPU_ALPHA_COLOR2);
+		writel_relaxed(h << 16 | w, dcrtc->base + LCD_SPU_HWC_HPXL_VLN);
+	}
+
+	writel_relaxed(yscr << 16 | xscr, dcrtc->base + LCD_SPU_HWC_OVSA_HPXL_VLN);
+
+	spin_lock_irq(&dcrtc->irq_lock);
+	armada_updatel(CFG_HWC_ENA,
+		     CFG_HWC_ENA | CFG_HWC_1BITMOD | CFG_HWC_1BITENA,
+		     dcrtc->base + LCD_SPU_DMA_CTRL0);
+	spin_unlock_irq(&dcrtc->irq_lock);
+
+	return 0;
+}
+
+static void cursor_update(void *data)
+{
+	armada_drm_crtc_cursor_update(data, true);
+}
+
+static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
+	struct drm_file *file, uint32_t handle, uint32_t w, uint32_t h)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_gem_object *obj = NULL;
+	int ret;
+
+	if (handle && w > 0 && h > 0) {
+		/* maximum size is 64x64 */
+		if (w > 64 || h > 64)
+			return -ENOMEM;
+
+		obj = armada_gem_object_lookup(dev, file, handle);
+		if (!obj)
+			return -ENOENT;
+
+		/* Must be a kernel-mapped object */
+		if (!obj->addr) {
+			drm_gem_object_unreference_unlocked(&obj->obj);
+			return -EINVAL;
+		}
+
+		if (obj->obj.size < w * h * 4) {
+			DRM_ERROR("buffer is too small\n");
+			drm_gem_object_unreference_unlocked(&obj->obj);
+			return -ENOMEM;
+		}
+	}
+
+	mutex_lock(&dev->struct_mutex);
+	if (dcrtc->cursor_obj) {
+		dcrtc->cursor_obj->update = NULL;
+		dcrtc->cursor_obj->update_data = NULL;
+		drm_gem_object_unreference(&dcrtc->cursor_obj->obj);
+	}
+	dcrtc->cursor_obj = obj;
+	dcrtc->cursor_w = w;
+	dcrtc->cursor_h = h;
+	ret = armada_drm_crtc_cursor_update(dcrtc, true);
+	if (obj) {
+		obj->update_data = dcrtc;
+		obj->update = cursor_update;
+	}
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+static int armada_drm_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	int ret;
+
+	mutex_lock(&dev->struct_mutex);
+	dcrtc->cursor_x = x;
+	dcrtc->cursor_y = y;
+	ret = armada_drm_crtc_cursor_update(dcrtc, false);
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+#else
+#define armada_drm_crtc_cursor_set NULL
+#define armada_drm_crtc_cursor_move NULL
+#endif
+
 static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
 {
 	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
 	struct armada_private *priv = crtc->dev->dev_private;
 
+	if (dcrtc->cursor_obj)
+		drm_gem_object_unreference(&dcrtc->cursor_obj->obj);
+
 	priv->dcrtc[dcrtc->num] = NULL;
 	drm_crtc_cleanup(&dcrtc->crtc);
 
@@ -708,6 +907,8 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
 }
 
 static struct drm_crtc_funcs armada_crtc_funcs = {
+	.cursor_set	= armada_drm_crtc_cursor_set,
+	.cursor_move	= armada_drm_crtc_cursor_move,
 	.destroy	= armada_drm_crtc_destroy,
 	.page_flip	= armada_drm_crtc_page_flip,
 	.set_config	= drm_crtc_helper_set_config,
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
index e00dcf5..16e9d93 100644
--- a/drivers/gpu/drm/armada/armada_crtc.h
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -48,6 +48,14 @@ struct armada_crtc {
 
 	struct armada_overlay	*overlay;
 
+	struct armada_gem_object	*cursor_obj;
+	int			cursor_x;
+	int			cursor_y;
+	uint32_t		cursor_w;
+	uint32_t		cursor_h;
+	uint32_t		cursor_lw;
+	uint32_t		cursor_lh;
+
 	int			dpms;
 	uint32_t		cfg_dma_ctrl0;
 	uint32_t		cfg_dumb_ctrl;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 3/4] DRM: Armada: convert Armada hardware cursor support to RGB+transparency
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-10 22:50   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:50 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/Kconfig       |    6 +-
 drivers/gpu/drm/armada/armada_crtc.c |  186 +++++++++++++++++++++++----------
 drivers/gpu/drm/armada/armada_crtc.h |    5 +-
 3 files changed, 135 insertions(+), 62 deletions(-)

diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
index 6f64642..c401339 100644
--- a/drivers/gpu/drm/armada/Kconfig
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -15,8 +15,8 @@ config DRM_ARMADA
 	  kernel mode setting and buffer management to userspace.
 
 config DRM_ARMADA_CURSOR
-	bool "Enable hardware cursor support for Marvell Armada DRM"
+	bool "Enable hardware RGB+T cursor support for Marvell Armada DRM"
 	depends on DRM_ARMADA != n
 	help
-	  Add support for hardware cursor support on the Marvell
-	  Armada devices.
+	  Add support for RGB+transparency hardware cursor support on
+	  the Marvell Armada devices.
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index 4a71ba0..b833014 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -369,8 +369,19 @@ void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
 		val = readl_relaxed(base + LCD_SPU_ADV_REG);
 		val &= ~(0xfff << 20 | 0xfff);
 		val |= dcrtc->v[i].spu_adv_reg;
-		writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG);
+		writel_relaxed(val, base + LCD_SPU_ADV_REG);
 	}
+
+	if (stat & DUMB_FRAMEDONE && dcrtc->cursor_update) {
+		writel_relaxed(dcrtc->cursor_hw_pos, base + LCD_SPU_HWC_OVSA_HPXL_VLN);
+		writel_relaxed(dcrtc->cursor_hw_sz, base + LCD_SPU_HWC_HPXL_VLN);
+		armada_updatel(CFG_HWC_ENA,
+			     CFG_HWC_ENA | CFG_HWC_1BITMOD | CFG_HWC_1BITENA,
+			     base + LCD_SPU_DMA_CTRL0);
+		dcrtc->cursor_update = false;
+		armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
+	}
+
 	spin_unlock(&dcrtc->irq_lock);
 
 	/* Only on frame 0 IRQs (start of progressive / odd frame) */
@@ -481,6 +492,9 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
 	val = adj->crtc_hsync_start;
 	dcrtc->v[1].spu_adv_reg = val << 20 | val;
 
+	/* Always enable RGB hardware cursor mode */
+	dcrtc->v[1].spu_adv_reg |= ADV_HWC32ENABLE;
+
 	if (interlaced) {
 		/* Odd interlaced frame */
 		dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total +
@@ -627,6 +641,103 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
 };
 
 #ifdef CONFIG_DRM_ARMADA_CURSOR
+static void armada_load_cursor_rgb(void __iomem *base, uint32_t *pix,
+	unsigned stride, unsigned width, unsigned height)
+{
+	uint32_t r, g, b, addr;
+	unsigned y, n;
+
+	for (r = g = b = addr = n = y = 0; y < height; y++) {
+		uint32_t *p = &pix[y * stride];
+		unsigned x;
+
+		for (x = 0; x < width; x++, p++) {
+			r >>= 8;
+			r |= 0xff000000 & (*p << 8);
+			g >>= 8;
+			g |= 0xff000000 & (*p << 16);
+			b >>= 8;
+			b |= 0xff000000 & (*p << 24);
+			if (x == 0 || y == 0) {
+				b |= 0xff000000;
+				g |= 0xff000000;
+				r &= ~0xff000000;
+			}
+			if (++n == 4) {
+				writel_relaxed(r,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_RAMR,
+					       base + LCD_SPU_SRAM_CTRL);
+				writel_relaxed(g,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_RAMG,
+					       base + LCD_SPU_SRAM_CTRL);
+				writel_relaxed(b,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_RAMB,
+					       base + LCD_SPU_SRAM_CTRL);
+				addr += 1;
+				if ((addr & 255) == 0)
+					addr += 0xf00;
+				n = 0;
+			}
+		}
+	}
+	if (n) {
+		r >>= 8 * (4 - n);
+		g >>= 8 * (4 - n);
+		b >>= 8 * (4 - n);
+		writel_relaxed(r, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMR,
+			       base + LCD_SPU_SRAM_CTRL);
+		writel_relaxed(g, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMG,
+			       base + LCD_SPU_SRAM_CTRL);
+		writel_relaxed(b, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMB,
+			       base + LCD_SPU_SRAM_CTRL);
+	}
+}
+
+static void armada_load_cursor_alpha(void __iomem *base, uint32_t *pix,
+	unsigned stride, unsigned width, unsigned height)
+{
+	uint32_t data, addr, ram_cmd = SRAM_WRITE | SRAM_HWC32_TRAN;
+	unsigned y, n;
+
+	for (data = addr = n = y = 0; y < height; y++) {
+		uint32_t *p = &pix[y * stride];
+		unsigned x;
+
+		for (x = 0; x < width; x++, p++) {
+			data >>= 2;
+			if (*p >= 0x80000000)
+				data |= 0x40000000;
+			if (x == 0 || y == 0)
+				data |= 0x40000000;
+			if (++n == 16) {
+				writel_relaxed(data,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_TRAN,
+					       base + LCD_SPU_SRAM_CTRL);
+				addr += 1;
+				if ((addr & 255) == 0)
+					addr += 0xf00;
+				n = 0;
+			}
+		}
+	}
+	if (n) {
+		data >>= 2 * (16 - n);
+		writel_relaxed(data, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | ram_cmd, base + LCD_SPU_SRAM_CTRL);
+	}
+}
+
 static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 {
 	uint32_t xoff, xscr, w = dcrtc->cursor_w, s;
@@ -672,6 +783,8 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 
 	if (!dcrtc->cursor_obj || !h || !w) {
 		spin_lock_irq(&dcrtc->irq_lock);
+		armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
+		dcrtc->cursor_update = false;
 		armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
 		spin_unlock_irq(&dcrtc->irq_lock);
 		return 0;
@@ -680,70 +793,29 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 	armada_updatel(CFG_CSB_256x32, CFG_PDWN256x32,
 		     dcrtc->base + LCD_SPU_SRAM_PARA1);
 
-	if (dcrtc->cursor_lw != w || dcrtc->cursor_lh != h || reload) {
+	if (dcrtc->cursor_hw_sz != (h << 16 | w) || reload) {
 		struct armada_gem_object *obj = dcrtc->cursor_obj;
-		uint32_t *pix, *p, col2 = 0, col3 = 0;
-		unsigned x, y, d, n, a;
-
-		dcrtc->cursor_lw = w;
-		dcrtc->cursor_lh = h;
+		uint32_t *pix;
 
-		pix = obj->addr;
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
+		dcrtc->cursor_update = false;
+		armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
 
 		/* Set the top-left corner of the cursor image */
+		pix = obj->addr;
 		pix += yoff * s + xoff;
-
-		a = 2 << 14 | 15 << 8;
-		for (d = n = y = 0; y < h; y++) {
-			for (x = 0, p = &pix[y * s]; x < w; x++, p++) {
-				uint32_t v = *p;
-				unsigned b;
-
-				if ((v & 0xff000000) != 0xff000000) {
-					b = 0;	/* transparent */
-				} else if (col2 == v) {
-					b = 2;	/* color 2 */
-				} else if (col3 == v) {
-					b = 3;	/* color 3 */
-				} else if (col2 == 0) {
-					col2 = v;
-					b = 2;	/* alloc color 2 */
-				} else if (col3 == 0) {
-					col3 = v;
-					b = 3;	/* alloc color 3 */
-				} else {
-					/* fail */
-					b = 1;	/* inverse (!) */
-				}
-
-				d |= b << n;
-				n += 2;
-
-				if (n == 32) {
-					writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
-					writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
-					a++;
-					d = n = 0;
-				}
-			}
-		}
-
-		if (n) {
-			writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
-			writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
-		}
-
-		writel_relaxed(col2, dcrtc->base + LCD_SPU_ALPHA_COLOR1);
-		writel_relaxed(col3, dcrtc->base + LCD_SPU_ALPHA_COLOR2);
-		writel_relaxed(h << 16 | w, dcrtc->base + LCD_SPU_HWC_HPXL_VLN);
+		armada_load_cursor_rgb(dcrtc->base, pix, s, w, h);
+		armada_load_cursor_alpha(dcrtc->base, pix, s, w, h);
 	}
 
-	writel_relaxed(yscr << 16 | xscr, dcrtc->base + LCD_SPU_HWC_OVSA_HPXL_VLN);
-
+	/* Reload the cursor position, size and enable in the IRQ handler */
 	spin_lock_irq(&dcrtc->irq_lock);
-	armada_updatel(CFG_HWC_ENA,
-		     CFG_HWC_ENA | CFG_HWC_1BITMOD | CFG_HWC_1BITENA,
-		     dcrtc->base + LCD_SPU_DMA_CTRL0);
+	dcrtc->cursor_hw_pos = yscr << 16 | xscr;
+	dcrtc->cursor_hw_sz = h << 16 | w;
+	dcrtc->cursor_update = true;
+	armada_drm_crtc_enable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
 	spin_unlock_irq(&dcrtc->irq_lock);
 
 	return 0;
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
index 16e9d93..817f29e 100644
--- a/drivers/gpu/drm/armada/armada_crtc.h
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -45,16 +45,17 @@ struct armada_crtc {
 		uint32_t	spu_adv_reg;
 	} v[2];
 	bool			interlaced;
+	bool			cursor_update;
 
 	struct armada_overlay	*overlay;
 
 	struct armada_gem_object	*cursor_obj;
 	int			cursor_x;
 	int			cursor_y;
+	uint32_t		cursor_hw_pos;
+	uint32_t		cursor_hw_sz;
 	uint32_t		cursor_w;
 	uint32_t		cursor_h;
-	uint32_t		cursor_lw;
-	uint32_t		cursor_lh;
 
 	int			dpms;
 	uint32_t		cfg_dma_ctrl0;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 3/4] DRM: Armada: convert Armada hardware cursor support to RGB+transparency
@ 2013-06-10 22:50   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:50 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/Kconfig       |    6 +-
 drivers/gpu/drm/armada/armada_crtc.c |  186 +++++++++++++++++++++++----------
 drivers/gpu/drm/armada/armada_crtc.h |    5 +-
 3 files changed, 135 insertions(+), 62 deletions(-)

diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
index 6f64642..c401339 100644
--- a/drivers/gpu/drm/armada/Kconfig
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -15,8 +15,8 @@ config DRM_ARMADA
 	  kernel mode setting and buffer management to userspace.
 
 config DRM_ARMADA_CURSOR
-	bool "Enable hardware cursor support for Marvell Armada DRM"
+	bool "Enable hardware RGB+T cursor support for Marvell Armada DRM"
 	depends on DRM_ARMADA != n
 	help
-	  Add support for hardware cursor support on the Marvell
-	  Armada devices.
+	  Add support for RGB+transparency hardware cursor support on
+	  the Marvell Armada devices.
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index 4a71ba0..b833014 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -369,8 +369,19 @@ void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
 		val = readl_relaxed(base + LCD_SPU_ADV_REG);
 		val &= ~(0xfff << 20 | 0xfff);
 		val |= dcrtc->v[i].spu_adv_reg;
-		writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG);
+		writel_relaxed(val, base + LCD_SPU_ADV_REG);
 	}
+
+	if (stat & DUMB_FRAMEDONE && dcrtc->cursor_update) {
+		writel_relaxed(dcrtc->cursor_hw_pos, base + LCD_SPU_HWC_OVSA_HPXL_VLN);
+		writel_relaxed(dcrtc->cursor_hw_sz, base + LCD_SPU_HWC_HPXL_VLN);
+		armada_updatel(CFG_HWC_ENA,
+			     CFG_HWC_ENA | CFG_HWC_1BITMOD | CFG_HWC_1BITENA,
+			     base + LCD_SPU_DMA_CTRL0);
+		dcrtc->cursor_update = false;
+		armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
+	}
+
 	spin_unlock(&dcrtc->irq_lock);
 
 	/* Only on frame 0 IRQs (start of progressive / odd frame) */
@@ -481,6 +492,9 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
 	val = adj->crtc_hsync_start;
 	dcrtc->v[1].spu_adv_reg = val << 20 | val;
 
+	/* Always enable RGB hardware cursor mode */
+	dcrtc->v[1].spu_adv_reg |= ADV_HWC32ENABLE;
+
 	if (interlaced) {
 		/* Odd interlaced frame */
 		dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total +
@@ -627,6 +641,103 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
 };
 
 #ifdef CONFIG_DRM_ARMADA_CURSOR
+static void armada_load_cursor_rgb(void __iomem *base, uint32_t *pix,
+	unsigned stride, unsigned width, unsigned height)
+{
+	uint32_t r, g, b, addr;
+	unsigned y, n;
+
+	for (r = g = b = addr = n = y = 0; y < height; y++) {
+		uint32_t *p = &pix[y * stride];
+		unsigned x;
+
+		for (x = 0; x < width; x++, p++) {
+			r >>= 8;
+			r |= 0xff000000 & (*p << 8);
+			g >>= 8;
+			g |= 0xff000000 & (*p << 16);
+			b >>= 8;
+			b |= 0xff000000 & (*p << 24);
+			if (x == 0 || y == 0) {
+				b |= 0xff000000;
+				g |= 0xff000000;
+				r &= ~0xff000000;
+			}
+			if (++n == 4) {
+				writel_relaxed(r,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_RAMR,
+					       base + LCD_SPU_SRAM_CTRL);
+				writel_relaxed(g,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_RAMG,
+					       base + LCD_SPU_SRAM_CTRL);
+				writel_relaxed(b,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_RAMB,
+					       base + LCD_SPU_SRAM_CTRL);
+				addr += 1;
+				if ((addr & 255) == 0)
+					addr += 0xf00;
+				n = 0;
+			}
+		}
+	}
+	if (n) {
+		r >>= 8 * (4 - n);
+		g >>= 8 * (4 - n);
+		b >>= 8 * (4 - n);
+		writel_relaxed(r, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMR,
+			       base + LCD_SPU_SRAM_CTRL);
+		writel_relaxed(g, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMG,
+			       base + LCD_SPU_SRAM_CTRL);
+		writel_relaxed(b, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMB,
+			       base + LCD_SPU_SRAM_CTRL);
+	}
+}
+
+static void armada_load_cursor_alpha(void __iomem *base, uint32_t *pix,
+	unsigned stride, unsigned width, unsigned height)
+{
+	uint32_t data, addr, ram_cmd = SRAM_WRITE | SRAM_HWC32_TRAN;
+	unsigned y, n;
+
+	for (data = addr = n = y = 0; y < height; y++) {
+		uint32_t *p = &pix[y * stride];
+		unsigned x;
+
+		for (x = 0; x < width; x++, p++) {
+			data >>= 2;
+			if (*p >= 0x80000000)
+				data |= 0x40000000;
+			if (x == 0 || y == 0)
+				data |= 0x40000000;
+			if (++n == 16) {
+				writel_relaxed(data,
+					       base + LCD_SPU_SRAM_WRDAT);
+				writel_relaxed(addr |
+					       SRAM_WRITE | SRAM_HWC32_TRAN,
+					       base + LCD_SPU_SRAM_CTRL);
+				addr += 1;
+				if ((addr & 255) == 0)
+					addr += 0xf00;
+				n = 0;
+			}
+		}
+	}
+	if (n) {
+		data >>= 2 * (16 - n);
+		writel_relaxed(data, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | ram_cmd, base + LCD_SPU_SRAM_CTRL);
+	}
+}
+
 static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 {
 	uint32_t xoff, xscr, w = dcrtc->cursor_w, s;
@@ -672,6 +783,8 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 
 	if (!dcrtc->cursor_obj || !h || !w) {
 		spin_lock_irq(&dcrtc->irq_lock);
+		armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
+		dcrtc->cursor_update = false;
 		armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
 		spin_unlock_irq(&dcrtc->irq_lock);
 		return 0;
@@ -680,70 +793,29 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 	armada_updatel(CFG_CSB_256x32, CFG_PDWN256x32,
 		     dcrtc->base + LCD_SPU_SRAM_PARA1);
 
-	if (dcrtc->cursor_lw != w || dcrtc->cursor_lh != h || reload) {
+	if (dcrtc->cursor_hw_sz != (h << 16 | w) || reload) {
 		struct armada_gem_object *obj = dcrtc->cursor_obj;
-		uint32_t *pix, *p, col2 = 0, col3 = 0;
-		unsigned x, y, d, n, a;
-
-		dcrtc->cursor_lw = w;
-		dcrtc->cursor_lh = h;
+		uint32_t *pix;
 
-		pix = obj->addr;
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_drm_crtc_disable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
+		dcrtc->cursor_update = false;
+		armada_updatel(0, CFG_HWC_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
 
 		/* Set the top-left corner of the cursor image */
+		pix = obj->addr;
 		pix += yoff * s + xoff;
-
-		a = 2 << 14 | 15 << 8;
-		for (d = n = y = 0; y < h; y++) {
-			for (x = 0, p = &pix[y * s]; x < w; x++, p++) {
-				uint32_t v = *p;
-				unsigned b;
-
-				if ((v & 0xff000000) != 0xff000000) {
-					b = 0;	/* transparent */
-				} else if (col2 == v) {
-					b = 2;	/* color 2 */
-				} else if (col3 == v) {
-					b = 3;	/* color 3 */
-				} else if (col2 == 0) {
-					col2 = v;
-					b = 2;	/* alloc color 2 */
-				} else if (col3 == 0) {
-					col3 = v;
-					b = 3;	/* alloc color 3 */
-				} else {
-					/* fail */
-					b = 1;	/* inverse (!) */
-				}
-
-				d |= b << n;
-				n += 2;
-
-				if (n == 32) {
-					writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
-					writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
-					a++;
-					d = n = 0;
-				}
-			}
-		}
-
-		if (n) {
-			writel_relaxed(d, dcrtc->base + LCD_SPU_SRAM_WRDAT);
-			writel_relaxed(a, dcrtc->base + LCD_SPU_SRAM_CTRL);
-		}
-
-		writel_relaxed(col2, dcrtc->base + LCD_SPU_ALPHA_COLOR1);
-		writel_relaxed(col3, dcrtc->base + LCD_SPU_ALPHA_COLOR2);
-		writel_relaxed(h << 16 | w, dcrtc->base + LCD_SPU_HWC_HPXL_VLN);
+		armada_load_cursor_rgb(dcrtc->base, pix, s, w, h);
+		armada_load_cursor_alpha(dcrtc->base, pix, s, w, h);
 	}
 
-	writel_relaxed(yscr << 16 | xscr, dcrtc->base + LCD_SPU_HWC_OVSA_HPXL_VLN);
-
+	/* Reload the cursor position, size and enable in the IRQ handler */
 	spin_lock_irq(&dcrtc->irq_lock);
-	armada_updatel(CFG_HWC_ENA,
-		     CFG_HWC_ENA | CFG_HWC_1BITMOD | CFG_HWC_1BITENA,
-		     dcrtc->base + LCD_SPU_DMA_CTRL0);
+	dcrtc->cursor_hw_pos = yscr << 16 | xscr;
+	dcrtc->cursor_hw_sz = h << 16 | w;
+	dcrtc->cursor_update = true;
+	armada_drm_crtc_enable_irq(dcrtc, DUMB_FRAMEDONE_ENA);
 	spin_unlock_irq(&dcrtc->irq_lock);
 
 	return 0;
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
index 16e9d93..817f29e 100644
--- a/drivers/gpu/drm/armada/armada_crtc.h
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -45,16 +45,17 @@ struct armada_crtc {
 		uint32_t	spu_adv_reg;
 	} v[2];
 	bool			interlaced;
+	bool			cursor_update;
 
 	struct armada_overlay	*overlay;
 
 	struct armada_gem_object	*cursor_obj;
 	int			cursor_x;
 	int			cursor_y;
+	uint32_t		cursor_hw_pos;
+	uint32_t		cursor_hw_sz;
 	uint32_t		cursor_w;
 	uint32_t		cursor_h;
-	uint32_t		cursor_lw;
-	uint32_t		cursor_lh;
 
 	int			dpms;
 	uint32_t		cfg_dma_ctrl0;
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 1/4] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 22:51   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:51 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds support for the pair of LCD controllers on the Marvell
Armada 510 SoCs.  This driver supports:
- multiple contiguous scanout buffers for video and graphics
- shm backed cacheable buffer objects for X pixmaps for Vivante GPU
  acceleration
- dual lcd0 and lcd1 crt operation
- video overlay on each LCD crt
- page flipping of the main scanout buffers

Included in this commit is the core driver with no output support; output
support is platform and encoder driver dependent.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/Kconfig                 |    2 +
 drivers/gpu/drm/Makefile                |    1 +
 drivers/gpu/drm/armada/Kconfig          |   15 +
 drivers/gpu/drm/armada/Makefile         |    6 +
 drivers/gpu/drm/armada/armada_crtc.c    |  766 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/armada/armada_crtc.h    |   73 +++
 drivers/gpu/drm/armada/armada_debugfs.c |  186 ++++++++
 drivers/gpu/drm/armada/armada_drm.h     |   73 +++
 drivers/gpu/drm/armada/armada_drv.c     |  389 ++++++++++++++++
 drivers/gpu/drm/armada/armada_fb.c      |  156 +++++++
 drivers/gpu/drm/armada/armada_fb.h      |   21 +
 drivers/gpu/drm/armada/armada_fbdev.c   |  210 +++++++++
 drivers/gpu/drm/armada/armada_gem.c     |  435 ++++++++++++++++++
 drivers/gpu/drm/armada/armada_gem.h     |   43 ++
 drivers/gpu/drm/armada/armada_hw.h      |  308 +++++++++++++
 drivers/gpu/drm/armada/armada_ioctl.h   |  124 +++++
 drivers/gpu/drm/armada/armada_ioctlP.h  |   22 +
 drivers/gpu/drm/armada/armada_output.c  |  159 +++++++
 drivers/gpu/drm/armada/armada_output.h  |   38 ++
 drivers/gpu/drm/armada/armada_overlay.c |  514 +++++++++++++++++++++
 drivers/gpu/drm/armada/armada_slave.c   |  138 ++++++
 drivers/gpu/drm/armada/armada_slave.h   |   26 +
 drivers/gpu/drm/armada/drm_helper.h     |   31 ++
 23 files changed, 3736 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/armada/Kconfig
 create mode 100644 drivers/gpu/drm/armada/Makefile
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.c
 create mode 100644 drivers/gpu/drm/armada/armada_crtc.h
 create mode 100644 drivers/gpu/drm/armada/armada_debugfs.c
 create mode 100644 drivers/gpu/drm/armada/armada_drm.h
 create mode 100644 drivers/gpu/drm/armada/armada_drv.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.c
 create mode 100644 drivers/gpu/drm/armada/armada_fb.h
 create mode 100644 drivers/gpu/drm/armada/armada_fbdev.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.c
 create mode 100644 drivers/gpu/drm/armada/armada_gem.h
 create mode 100644 drivers/gpu/drm/armada/armada_hw.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctl.h
 create mode 100644 drivers/gpu/drm/armada/armada_ioctlP.h
 create mode 100644 drivers/gpu/drm/armada/armada_output.c
 create mode 100644 drivers/gpu/drm/armada/armada_output.h
 create mode 100644 drivers/gpu/drm/armada/armada_overlay.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.c
 create mode 100644 drivers/gpu/drm/armada/armada_slave.h
 create mode 100644 drivers/gpu/drm/armada/drm_helper.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 1e82882..ae8a57f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
 
 source "drivers/gpu/drm/cirrus/Kconfig"
 
+source "drivers/gpu/drm/armada/Kconfig"
+
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d59b24..b458168 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
+obj-$(CONFIG_DRM_ARMADA) += armada/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-$(CONFIG_DRM_OMAP)	+= omapdrm/
diff --git a/drivers/gpu/drm/armada/Kconfig b/drivers/gpu/drm/armada/Kconfig
new file mode 100644
index 0000000..c7a0a94
--- /dev/null
+++ b/drivers/gpu/drm/armada/Kconfig
@@ -0,0 +1,15 @@
+config DRM_ARMADA
+	tristate "DRM support for Marvell Armada SoCs"
+	depends on DRM && HAVE_CLK
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select DRM_KMS_HELPER
+	help
+	  Support the "LCD" controllers found on the Marvell Armada 510
+	  devices.  There are two controllers on the device, each controller
+	  supports graphics and video overlays.
+
+	  This driver provides no built-in acceleration; acceleration is
+	  performed by other IP found on the SoC.  This driver provides
+	  kernel mode setting and buffer management to userspace.
diff --git a/drivers/gpu/drm/armada/Makefile b/drivers/gpu/drm/armada/Makefile
new file mode 100644
index 0000000..430f025
--- /dev/null
+++ b/drivers/gpu/drm/armada/Makefile
@@ -0,0 +1,6 @@
+armada-y	:= armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
+		   armada_gem.o armada_output.o armada_overlay.o \
+		   armada_slave.o
+armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o
+
+obj-$(CONFIG_DRM_ARMADA) := armada.o
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
new file mode 100644
index 0000000..dadaf63
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -0,0 +1,766 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+
+struct armada_frame_work {
+	struct drm_pending_vblank_event *event;
+	struct armada_regs regs[4];
+	struct drm_framebuffer *old_fb;
+	struct work_struct work;
+};
+
+/*
+ * A note about interlacing.  Let's consider HDMI 1920x1080i.
+ * The timing parameters we have from X are:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640  1080 1084 1094 1125
+ * Which get translated to:
+ *  Hact HsyA HsyI Htot  Vact VsyA VsyI Vtot
+ *  1920 2448 2492 2640   540  542  547  562
+ *
+ * This is how it is defined by CEA-861-D - line and pixel numbers are
+ * referenced to the rising edge of VSYNC and HSYNC.  Total clocks per
+ * line: 2640.  The odd frame, the first active line is at line 21, and
+ * the even frame, the first active line is 584.
+ *
+ * LN:    560     561     562     563             567     568    569
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
+ *  22 blanking lines.  VSYNC at 1320 (referenced to the HSYNC rising edge).
+ *
+ * LN:    1123   1124    1125      1               5       6      7
+ * DE:    ~~~|____________________________//__________________________
+ * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
+ * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
+ *  23 blanking lines
+ *
+ * The Armada LCD Controller line and pixel numbers are, like X timings,
+ * referenced to the top left of the active frame.
+ *
+ * So, translating these to our LCD controller:
+ *  Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
+ *  Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
+ * Note: Vsync front porch remains constant!
+ *
+ * if (odd_frame) {
+ *   vtotal = mode->crtc_vtotal + 1;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
+ *   vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
+ * } else {
+ *   vtotal = mode->crtc_vtotal;
+ *   vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
+ *   vhorizpos = mode->crtc_hsync_start;
+ * }
+ * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
+ *
+ * So, we need to reprogram these registers on each vsync event:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *
+ * Note: we do not use the frame done interrupts because these appear
+ * to happen too early, and lead to jitter on the display (presumably
+ * they occur at the end of the last active line, before the vsync back
+ * porch, which we're reprogramming.)
+ */
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+void armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs)
+{
+	while (regs->offset != ~0) {
+		void __iomem *reg = dcrtc->base + regs->offset;
+		uint32_t val;
+
+		val = regs->mask;
+		if (val != 0)
+			val &= readl_relaxed(reg);
+		writel_relaxed(val | regs->val, reg);
+		++regs;
+	}
+}
+
+#define dpms_blanked(dpms)	((dpms) != DRM_MODE_DPMS_ON)
+
+static void armada_drm_crtc_update(struct armada_crtc *dcrtc)
+{
+	uint32_t dumb_ctrl;
+
+	dumb_ctrl = dcrtc->cfg_dumb_ctrl;
+
+	if (!dpms_blanked(dcrtc->dpms))
+		dumb_ctrl |= CFG_DUMB_ENA;
+
+	/*
+	 * When a dumb interface isn't under 24bit, it might be
+	 * under SPI or GPIO.  If set to 7, this will force
+	 * LCD_D[23:0] to output blank color and damage GPIO
+	 * and SPI behaviour.  So leave it as-is unless in
+	 * DUMB24_RGB888_0 mode.
+	 */
+	if (dpms_blanked(dcrtc->dpms) &&
+	    (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
+		dumb_ctrl &= ~DUMB_MASK;
+		dumb_ctrl |= DUMB_BLANK;
+	}
+
+	/*
+	 * The documentation doesn't indicate what the normal state
+	 * of the sync signals are.  Sebastian Hesselbart kindly probed
+	 * his hardware to determine their state.
+	 *
+	 * The non-inverted state of the sync signals is active high.
+	 * Setting these bits makes the appropriate signal active low.
+	 */
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC)
+		dumb_ctrl |= CFG_INV_CSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC)
+		dumb_ctrl |= CFG_INV_HSYNC;
+	if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC)
+		dumb_ctrl |= CFG_INV_VSYNC;
+
+	if (dcrtc->dumb_ctrl != dumb_ctrl) {
+		dcrtc->dumb_ctrl = dumb_ctrl;
+		writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+}
+
+static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb, int x, int y,
+	struct armada_regs *regs, bool interlaced)
+{
+	struct armada_gem_object *obj = drm_fb_obj(fb);
+	unsigned pitch = fb->pitches[0];
+	unsigned offset = y * pitch + x * fb->bits_per_pixel / 8;
+	uint32_t addr_odd, addr_even;
+	unsigned i = 0;
+
+	DRM_DEBUG_DRIVER("pitch %u x %d y %d bpp %d\n",
+		pitch, x, y, fb->bits_per_pixel);
+
+	addr_odd = addr_even = obj->dev_addr + offset;
+
+	if (interlaced) {
+		addr_even += pitch;
+		pitch *= 2;
+	}
+
+	/* write offset, base, and pitch */
+	armada_reg_queue_set(regs, i, addr_odd, LCD_CFG_GRA_START_ADDR0);
+	armada_reg_queue_set(regs, i, addr_even, LCD_CFG_GRA_START_ADDR1);
+	armada_reg_queue_mod(regs, i, pitch, 0xffff, LCD_CFG_GRA_PITCH);
+
+	return i;
+}
+
+static void armada_drm_unref_work(struct work_struct *_work)
+{
+	struct armada_frame_work *work =
+		container_of(_work, struct armada_frame_work, work);
+	drm_framebuffer_unreference(work->old_fb);
+	kfree(work);
+}
+
+static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
+	struct armada_frame_work *work)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	unsigned long flags;
+	int ret;
+
+	INIT_WORK(&work->work, armada_drm_unref_work);
+
+	ret = drm_vblank_get(dev, dcrtc->num);
+	if (ret) {
+		DRM_ERROR("failed to acquire vblank counter\n");
+		return ret;
+	}
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (!dcrtc->frame_work) {
+		dcrtc->frame_work = work;
+	} else {
+		ret = -EBUSY;
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+
+	if (ret)
+		drm_vblank_put(dev, dcrtc->num);
+
+	return ret;
+}
+
+static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+	struct armada_frame_work *work = dcrtc->frame_work;
+
+	dcrtc->frame_work = NULL;
+
+	armada_drm_crtc_update_regs(dcrtc, work->regs);
+
+	if (work->event)
+		drm_send_vblank_event(dev, dcrtc->num, work->event);
+
+	drm_vblank_put(dev, dcrtc->num);
+
+	/* Finally, queue the process-half of the cleanup */
+	schedule_work(&work->work);
+}
+
+static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
+	struct drm_framebuffer *fb, bool force)
+{
+	struct armada_gem_object *obj;
+	struct armada_frame_work *work;
+
+	if (!fb)
+		return;
+
+	obj = drm_fb_obj(fb);
+	if (force) {
+		/* Display is disabled, so just drop the old fb */
+		drm_framebuffer_unreference(fb);
+		return;
+	}
+
+	work = kmalloc(sizeof *work, GFP_KERNEL);
+	if (work) {
+		int i = 0;
+		work->event = NULL;
+		work->old_fb = fb;
+		armada_reg_queue_end(work->regs, i);
+
+		if (armada_drm_crtc_queue_frame_work(dcrtc, work) == 0)
+			return;
+
+		kfree(work);
+	}
+
+	/*
+	 * Oops - just drop the reference immediately and hope for
+	 * the best.  The worst that will happen is the buffer gets
+	 * reused before it has finished being displayed.
+	 */
+	drm_framebuffer_unreference(fb);
+}
+
+static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
+{
+	struct drm_device *dev = dcrtc->crtc.dev;
+
+	/*
+	 * Tell the DRM core that vblank IRQs aren't going to happen for
+	 * a while.  This cleans up any pending vblank events for us.
+	 */
+	drm_vblank_off(dev, dcrtc->num);
+
+	/* Handle any pending flip event. */
+	spin_lock_irq(&dev->event_lock);
+	if (dcrtc->frame_work)
+		armada_drm_crtc_complete_frame_work(dcrtc);
+	spin_unlock_irq(&dev->event_lock);
+}
+
+void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b, int idx)
+{
+}
+
+void armada_drm_crtc_gamma_get(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, int idx)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_dpms(struct drm_crtc *crtc, int dpms)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != dpms) {
+		dcrtc->dpms = dpms;
+		armada_drm_crtc_update(dcrtc);
+		if (dpms_blanked(dpms))
+			armada_drm_vblank_off(dcrtc);
+	}
+}
+
+/*
+ * Prepare for a mode set.  Turn off overlay to ensure that we don't end
+ * up with the overlay size being bigger than the active screen size.
+ * We rely upon X refreshing this state after the mode set has completed.
+ *
+ * The mode_config.mutex will be held for this call
+ */
+static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	mutex_lock(&dev->struct_mutex);
+	if (dcrtc->overlay)
+		armada_drm_overlay_off(dev, dcrtc->overlay);
+	mutex_unlock(&dev->struct_mutex);
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_commit(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_ON) {
+		dcrtc->dpms = DRM_MODE_DPMS_ON;
+		armada_drm_crtc_update(dcrtc);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj)
+{
+	return true;
+}
+
+void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
+{
+	struct armada_vbl_event *e, *n;
+	void __iomem *base = dcrtc->base;
+
+	if (stat & DMA_FF_UNDERFLOW)
+		DRM_ERROR("video underflow on crtc %u\n", dcrtc->num);
+	if (stat & GRA_FF_UNDERFLOW)
+		DRM_ERROR("graphics underflow on crtc %u\n", dcrtc->num);
+
+	if (stat & VSYNC_IRQ)
+		drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num);
+
+	spin_lock(&dcrtc->irq_lock);
+
+	list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) {
+		list_del_init(&e->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		e->fn(dcrtc, e->data);
+	}
+
+	if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) {
+		int i = stat & GRA_FRAME_IRQ0 ? 0 : 1;
+		uint32_t val;
+
+		writel_relaxed(dcrtc->v[i].spu_v_porch, base + LCD_SPU_V_PORCH);
+		writel_relaxed(dcrtc->v[i].spu_v_h_total, base + LCD_SPUT_V_H_TOTAL);
+
+		val = readl_relaxed(base + LCD_SPU_ADV_REG);
+		val &= ~(0xfff << 20 | 0xfff);
+		val |= dcrtc->v[i].spu_adv_reg;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG);
+	}
+	spin_unlock(&dcrtc->irq_lock);
+
+	/* Only on frame 0 IRQs (start of progressive / odd frame) */
+	if (stat & GRA_FRAME_IRQ0) {
+		struct drm_device *dev = dcrtc->crtc.dev;
+
+		spin_lock(&dev->event_lock);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock(&dev->event_lock);
+
+		wake_up(&dcrtc->frame_wait);
+	}
+}
+
+/* These are locked by dev->vbl_lock */
+void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if (dcrtc->irq_ena & mask) {
+		dcrtc->irq_ena &= ~mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	}
+}
+
+void armada_drm_crtc_enable_irq(struct armada_crtc *dcrtc, u32 mask)
+{
+	if ((dcrtc->irq_ena & mask) != mask) {
+		dcrtc->irq_ena |= mask;
+		writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+		if (readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR) & mask)
+			writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	}
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
+	struct drm_display_mode *mode, struct drm_display_mode *adj,
+	int x, int y, struct drm_framebuffer *old_fb)
+{
+	struct armada_private *priv = crtc->dev->dev_private;
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[16];
+	uint32_t lm, rm, tm, bm, val, sclk;
+	unsigned long flags;
+	unsigned i;
+	bool interlaced;
+	int ret;
+
+	/* First, check that this will succeed. */
+	ret = priv->ops->compute_crtc_clock(dcrtc, adj, NULL);
+	if (ret)
+		return ret;
+
+	drm_framebuffer_reference(crtc->fb);
+
+	interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
+
+	i = armada_drm_crtc_calc_fb(dcrtc->crtc.fb, x, y, regs, interlaced);
+
+	rm = adj->crtc_hsync_start - adj->crtc_hdisplay;
+	lm = adj->crtc_htotal - adj->crtc_hsync_end;
+	bm = adj->crtc_vsync_start - adj->crtc_vdisplay;
+	tm = adj->crtc_vtotal - adj->crtc_vsync_end;
+
+	DRM_DEBUG_DRIVER("H: %d %d %d %d lm %d rm %d\n",
+		adj->crtc_hdisplay,
+		adj->crtc_hsync_start,
+		adj->crtc_hsync_end,
+		adj->crtc_htotal, lm, rm);
+	DRM_DEBUG_DRIVER("V: %d %d %d %d tm %d bm %d\n",
+		adj->crtc_vdisplay,
+		adj->crtc_vsync_start,
+		adj->crtc_vsync_end,
+		adj->crtc_vtotal, tm, bm);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	drm_vblank_pre_modeset(crtc->dev, dcrtc->num);
+
+	crtc->mode = *adj;
+
+	val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA;
+	if (val != dcrtc->dumb_ctrl) {
+		dcrtc->dumb_ctrl = val;
+		writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
+	}
+
+	/* Now compute the divider for real */
+	priv->ops->compute_crtc_clock(dcrtc, adj, &sclk);
+
+	armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV);
+
+	if (interlaced ^ dcrtc->interlaced) {
+		if (adj->flags & DRM_MODE_FLAG_INTERLACE)
+			drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+		else
+			drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+		dcrtc->interlaced = interlaced;
+	}
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+
+	/* Even interlaced/progressive frame */
+	dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 |
+				    adj->crtc_htotal;
+	dcrtc->v[1].spu_v_porch = tm << 16 | bm;
+	val = adj->crtc_hsync_start;
+	dcrtc->v[1].spu_adv_reg = val << 20 | val;
+
+	if (interlaced) {
+		/* Odd interlaced frame */
+		dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total +
+						(1 << 16);
+		dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1;
+		val = adj->crtc_hsync_start - adj->crtc_htotal / 2;
+		dcrtc->v[0].spu_adv_reg = val << 20 | val;
+	} else {
+		dcrtc->v[0] = dcrtc->v[1];
+	}
+
+	val = (adj->crtc_vdisplay << 16) | adj->crtc_hdisplay;
+
+	armada_reg_queue_set(regs, i, val, LCD_SPU_V_H_ACTIVE);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GRA_HPXL_VLN);
+	armada_reg_queue_set(regs, i, val, LCD_SPU_GZM_HPXL_VLN);
+	armada_reg_queue_set(regs, i, (lm << 16) | rm, LCD_SPU_H_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_porch, LCD_SPU_V_PORCH);
+	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total,
+			   LCD_SPUT_V_H_TOTAL);
+	armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg | ADV_VSYNCOFFEN,
+			   (0xfff << 20 | 0xfff), LCD_SPU_ADV_REG);
+
+	val = dcrtc->cfg_dma_ctrl0;
+
+	switch (crtc->fb->pixel_format) {
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+		val |= CFG_GRA_1555;
+		break;
+	case DRM_FORMAT_RGB565:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR565:
+		val |= CFG_GRA_565;
+		break;
+	case DRM_FORMAT_RGB888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_BGR888:
+		val |= CFG_GRA_888PACK;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_XBGR8888:
+		val |= CFG_GRA_X888;
+		break;
+	case DRM_FORMAT_ARGB8888:
+		val ^= CFG_GRA_SWAPRB;
+	case DRM_FORMAT_ABGR8888:
+		val |= CFG_GRA_8888;
+		break;
+	case DRM_FORMAT_C8:
+		val |= CFG_GRA_PSEUDO8 | CFG_PALETTE_ENA;
+		break;
+	}
+	if (interlaced)
+		val |= CFG_GRA_FTOGGLE;
+	armada_reg_queue_mod(regs, i, val, CFG_GRAFORMAT | CFG_GRA_SWAPRB |
+			   CFG_PALETTE_ENA | CFG_GRA_FTOGGLE,
+			   LCD_SPU_DMA_CTRL0);
+
+	val = adj->flags & DRM_MODE_FLAG_NVSYNC ? CFG_VSYNC_INV : 0;
+	armada_reg_queue_mod(regs, i, val, CFG_VSYNC_INV, LCD_SPU_DMA_CTRL1);
+
+	/*
+	 * Set the colorimetry, based upon the HDMI spec.
+	 * 1280x720p, 1920x1080p and 1920x1080i use ITU709, others
+	 * use ITU601.  In any case, we select professional.
+	 */
+	if ((adj->hdisplay == 1280 && adj->vdisplay == 720 && !interlaced) ||
+	    (adj->hdisplay == 1920 && adj->vdisplay == 1080)) {
+		val = CFG_CSC_PROF | CFG_CSC_CCIR709;
+	} else {
+		val = CFG_CSC_PROF;
+	}
+	armada_reg_queue_mod(regs, i, val, CFG_CSC_MASK, LCD_SPU_IOPAD_CONTROL);
+	armada_reg_queue_end(regs, i);
+
+	armada_drm_crtc_update_regs(dcrtc, regs);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+
+	armada_drm_crtc_update(dcrtc);
+
+	drm_vblank_post_modeset(crtc->dev, dcrtc->num);
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+/* The mode_config.mutex will be held for this call */
+static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+	struct drm_framebuffer *old_fb)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_regs regs[4];
+	unsigned i;
+
+	i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, dcrtc->interlaced);
+	armada_reg_queue_end(regs, i);
+
+	/* Wait for pending flips to complete */
+	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
+
+	/* Take a reference to the new fb as we're using it */
+	drm_framebuffer_reference(crtc->fb);
+
+	/* Update the base in the CRTC */
+	armada_drm_crtc_update_regs(dcrtc, regs);
+
+	/* Drop our previously held reference */
+	armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+
+	return 0;
+}
+
+static void armada_drm_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+/* The mode_config.mutex will be held for this call */
+static void armada_drm_crtc_disable(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+
+	if (dcrtc->dpms != DRM_MODE_DPMS_OFF) {
+		dcrtc->dpms = DRM_MODE_DPMS_OFF;
+		armada_drm_crtc_update(dcrtc);
+	}
+	armada_drm_vblank_off(dcrtc);
+	armada_drm_crtc_finish_fb(dcrtc, crtc->fb, true);
+}
+
+static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
+	.dpms		= armada_drm_crtc_dpms,
+	.prepare	= armada_drm_crtc_prepare,
+	.commit		= armada_drm_crtc_commit,
+	.mode_fixup	= armada_drm_crtc_mode_fixup,
+	.mode_set	= armada_drm_crtc_mode_set,
+	.mode_set_base	= armada_drm_crtc_mode_set_base,
+	.load_lut	= armada_drm_crtc_load_lut,
+	.disable	= armada_drm_crtc_disable,
+};
+
+static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_private *priv = crtc->dev->dev_private;
+
+	priv->dcrtc[dcrtc->num] = NULL;
+	drm_crtc_cleanup(&dcrtc->crtc);
+
+	if (!IS_ERR(dcrtc->clk))
+		clk_disable_unprepare(dcrtc->clk);
+
+	kfree(dcrtc);
+}
+
+/*
+ * The mode_config lock is held here, to prevent races between this
+ * and a mode_set.
+ */
+static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
+	struct drm_framebuffer *fb, struct drm_pending_vblank_event *event)
+{
+	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct armada_frame_work *work;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+	unsigned i;
+	int ret;
+
+	/* We don't support changing the pixel format */
+	if (fb->pixel_format != crtc->fb->pixel_format)
+		return -EINVAL;
+
+	work = kmalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return -ENOMEM;
+
+	work->event = event;
+	work->old_fb = dcrtc->crtc.fb;
+
+	i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs, dcrtc->interlaced);
+	armada_reg_queue_end(work->regs, i);
+
+	/*
+	 * Hold the old framebuffer for the work - DRM appears to drop our
+	 * reference to the old framebuffer in drm_mode_page_flip_ioctl().
+	 */
+	drm_framebuffer_reference(work->old_fb);
+
+	ret = armada_drm_crtc_queue_frame_work(dcrtc, work);
+	if (ret) {
+		/*
+		 * Undo our reference above; DRM does not drop the reference
+		 * to this object on error, so that's okay.
+		 */
+		drm_framebuffer_unreference(work->old_fb);
+		kfree(work);
+		return ret;
+	}
+
+	/*
+	 * Don't take a reference on the new framebuffer;
+	 * drm_mode_page_flip_ioctl() has already grabbed a reference and
+	 * will _not_ drop that reference on successful return from this
+	 * function.  Simply mark this new framebuffer as the current one.
+	 */
+	dcrtc->crtc.fb = fb;
+
+	/*
+	 * Finally, if the display is blanked, we won't receive an
+	 * interrupt, so complete it now.
+	 */
+	if (dpms_blanked(dcrtc->dpms)) {
+		spin_lock_irqsave(&dev->event_lock, flags);
+		if (dcrtc->frame_work)
+			armada_drm_crtc_complete_frame_work(dcrtc);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+
+	return 0;
+}
+
+static struct drm_crtc_funcs armada_crtc_funcs = {
+	.destroy	= armada_drm_crtc_destroy,
+	.page_flip	= armada_drm_crtc_page_flip,
+	.set_config	= drm_crtc_helper_set_config,
+};
+
+int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
+	struct resource *res)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc;
+	void __iomem *base;
+
+	base = devm_request_and_ioremap(dev->dev, res);
+	if (!base) {
+		DRM_ERROR("failed to ioremap register\n");
+		return -ENOMEM;
+	}
+
+	dcrtc = kzalloc(sizeof(*dcrtc), GFP_KERNEL);
+	if (!dcrtc) {
+		DRM_ERROR("failed to allocate Armada crtc\n");
+		return -ENOMEM;
+	}
+
+	dcrtc->base = base;
+	dcrtc->num = num;
+	dcrtc->clk = ERR_PTR(-EINVAL);
+	dcrtc->cfg_dma_ctrl0 = CFG_GRA_ENA | CFG_GRA_HSMOOTH;
+	dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0;
+	spin_lock_init(&dcrtc->irq_lock);
+	dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR;
+	INIT_LIST_HEAD(&dcrtc->vbl_list);
+	init_waitqueue_head(&dcrtc->frame_wait);
+
+	/* Initialize some registers which we don't otherwise set */
+	writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_BLANKCOLOR);
+	writel_relaxed(CFG_IOPAD_DUMB24, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_SRAM_PARA0);
+	writel_relaxed(0x0000e000, dcrtc->base + LCD_SPU_SRAM_PARA1);
+	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
+	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
+
+	/* Lower the watermark so to eliminate jitter at higher bandwidths */
+	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
+
+	/* Ensure AXI pipeline is enabled */
+	armada_updatel(CFG_ARBFAST_ENA, 0, dcrtc->base + LCD_SPU_DMA_CTRL0);
+
+	priv->dcrtc[dcrtc->num] = dcrtc;
+
+	drm_crtc_init(dev, &dcrtc->crtc, &armada_crtc_funcs);
+	drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h
new file mode 100644
index 0000000..e00dcf5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_crtc.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CRTC_H
+#define ARMADA_CRTC_H
+
+struct armada_overlay;
+struct armada_gem_object;
+
+struct armada_regs {
+	uint32_t offset;
+	uint32_t mask;
+	uint32_t val;
+};
+
+#define armada_reg_queue_mod(_r, _i, _v, _m, _o)	\
+	do {					\
+		struct armada_regs *__reg = _r;	\
+		__reg[_i].offset = _o;		\
+		__reg[_i].mask = ~(_m);		\
+		__reg[_i].val = _v;		\
+		_i++;				\
+	} while (0)
+
+#define armada_reg_queue_set(_r, _i, _v, _o)	\
+	armada_reg_queue_mod(_r, _i, _v, ~0, _o)
+
+#define armada_reg_queue_end(_r, _i)		\
+	armada_reg_queue_mod(_r, _i, 0, 0, ~0)
+
+struct armada_frame_work;
+
+struct armada_crtc {
+	struct drm_crtc		crtc;
+	unsigned		num;
+	void __iomem		*base;
+	struct clk		*clk;
+	struct {
+		uint32_t	spu_v_h_total;
+		uint32_t	spu_v_porch;
+		uint32_t	spu_adv_reg;
+	} v[2];
+	bool			interlaced;
+
+	struct armada_overlay	*overlay;
+
+	int			dpms;
+	uint32_t		cfg_dma_ctrl0;
+	uint32_t		cfg_dumb_ctrl;
+	uint32_t		dumb_ctrl;
+
+	wait_queue_head_t	frame_wait;
+	struct armada_frame_work	*frame_work;
+
+	spinlock_t		irq_lock;
+	uint32_t		irq_ena;
+	struct list_head	vbl_list;
+};
+#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)
+
+int armada_drm_crtc_create(struct drm_device *, unsigned, struct resource *);
+void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
+void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
+void armada_drm_crtc_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
+void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_debugfs.c b/drivers/gpu/drm/armada/armada_debugfs.c
new file mode 100644
index 0000000..ff5ada3
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_debugfs.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <drm/drmP.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+
+static int armada_debugfs_gem_offs_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct drm_gem_mm *mm = dev->mm_private;
+
+	return drm_mm_dump_table(m, &mm->offset_manager);
+}
+
+static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct armada_private *priv = node->minor->dev->dev_private;
+
+	return drm_mm_dump_table(m, &priv->linear);
+}
+
+static int armada_debugfs_reg_show(struct seq_file *m, void *data)
+{
+	struct drm_device *dev = m->private;
+	struct armada_private *priv = dev->dev_private;
+	int n, i;
+
+	if (priv) {
+		for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+			struct armada_crtc *dcrtc = priv->dcrtc[n];
+			if (!dcrtc)
+				continue;
+
+			for (i = 0x84; i <= 0x1c4; i += 4) {
+				uint32_t v = readl_relaxed(dcrtc->base + i);
+				seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, armada_debugfs_reg_show, inode->i_private);
+}
+
+static const struct file_operations fops_reg_r = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_r_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int armada_debugfs_write(struct file *file, const char __user *ptr,
+	size_t len, loff_t *off)
+{
+	struct drm_device *dev = file->private_data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	char buf[32], *p;
+	uint32_t reg, val;
+	int ret;
+
+	if (*off != 0)
+		return 0;
+
+	if (len > sizeof(buf) - 1)
+		len = sizeof(buf) - 1;
+
+	ret = strncpy_from_user(buf, ptr, len);
+	if (ret < 0)
+		return ret;
+	buf[len] = '\0';
+
+	reg = simple_strtoul(buf, &p, 16);
+	if (!isspace(*p))
+		return -EINVAL;
+	val = simple_strtoul(p + 1, NULL, 16);
+
+	if (reg >= 0x84 && reg <= 0x1c4)
+		writel(val, dcrtc->base + reg);
+
+	return len;
+}
+
+static int armada_debugfs_reg_w_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static const struct file_operations fops_reg_w = {
+	.owner = THIS_MODULE,
+	.open = armada_debugfs_reg_w_open,
+	.write = armada_debugfs_write,
+	.llseek = noop_llseek,
+};
+
+static struct drm_info_list armada_debugfs_list[] = {
+	{ "gem_linear", armada_debugfs_gem_linear_show, 0 },
+	{ "gem_offset", armada_debugfs_gem_offs_show, 0 },
+};
+#define ARMADA_DEBUGFS_ENTRIES ARRAY_SIZE(armada_debugfs_list)
+
+static int drm_add_fake_info_node(struct drm_minor *minor, struct dentry *ent,
+	const void *key)
+{
+	struct drm_info_node *node;
+
+	node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL);
+	if (node == NULL) {
+		debugfs_remove(ent);
+		return -ENOMEM;
+	}
+
+	node->minor = minor;
+	node->dent = ent;
+	node->info_ent = (void *) key;
+
+	mutex_lock(&minor->debugfs_lock);
+	list_add(&node->list, &minor->debugfs_list);
+	mutex_unlock(&minor->debugfs_lock);
+
+	return 0;
+}
+
+static int armada_debugfs_create(struct dentry *root, struct drm_minor *minor,
+	const char *name, umode_t mode, const struct file_operations *fops)
+{
+	struct dentry *de;
+
+	de = debugfs_create_file(name, mode, root, minor->dev, fops);
+
+	return drm_add_fake_info_node(minor, de, fops);
+}
+
+int armada_drm_debugfs_init(struct drm_minor *minor)
+{
+	int ret;
+
+	ret = drm_debugfs_create_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				       minor->debugfs_root, minor);
+	if (ret)
+		return ret;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				   "reg", S_IFREG | S_IRUSR, &fops_reg_r);
+	if (ret)
+		goto err_1;
+
+	ret = armada_debugfs_create(minor->debugfs_root, minor,
+				"reg_wr", S_IFREG | S_IWUSR, &fops_reg_w);
+	if (ret)
+		goto err_2;
+	return ret;
+
+ err_2:
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+ err_1:
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+	return ret;
+}
+
+void armada_drm_debugfs_cleanup(struct drm_minor *minor)
+{
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_w, 1, minor);
+	drm_debugfs_remove_files((struct drm_info_list *) &fops_reg_r, 1, minor);
+	drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
+				 minor);
+}
diff --git a/drivers/gpu/drm/armada/armada_drm.h b/drivers/gpu/drm/armada/armada_drm.h
new file mode 100644
index 0000000..c2dcde5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drm.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_DRM_H
+#define ARMADA_DRM_H
+
+struct armada_crtc;
+struct armada_gem_object;
+struct armada_overlay;
+struct clk;
+
+static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp)
+{
+	uint32_t pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2;
+
+	/* 88AP510 spec recommends pitch be a multiple of 128 */
+	return ALIGN(pitch, 128);
+}
+
+struct armada_vbl_event {
+	struct list_head	node;
+	void			*data;
+	void			(*fn)(struct armada_crtc *, void *);
+};
+void armada_drm_vbl_event_add(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove(struct armada_crtc *, struct armada_vbl_event *);
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *, struct armada_vbl_event *);
+#define armada_drm_vbl_event_init(_e,_f,_d) do {	\
+	struct armada_vbl_event *__e = _e;	\
+	INIT_LIST_HEAD(&__e->node);		\
+	__e->data = _d;				\
+	__e->fn = _f;				\
+} while (0)
+
+
+struct armada_private;
+
+struct armada_ops {
+	int (*init)(struct armada_private *, struct device *);
+	int (*compute_crtc_clock)(struct armada_crtc *,
+				  const struct drm_display_mode *,
+				  uint32_t *);
+};
+
+struct armada_private {
+	const struct armada_ops	*ops;
+	struct drm_fb_helper	*fbdev;
+	struct armada_crtc	*dcrtc[2];
+	struct armada_overlay	*overlay;
+	struct drm_mm		linear;
+	struct clk		*extclk[2];
+#ifdef CONFIG_DEBUG_FS
+	struct dentry		*de;
+#endif
+};
+
+extern const struct drm_mode_config_funcs armada_drm_mode_config_funcs;
+
+int armada_fbdev_init(struct drm_device *);
+void armada_fbdev_fini(struct drm_device *);
+
+int armada_overlay_create(struct drm_device *);
+void armada_overlay_destroy(struct drm_device *);
+void armada_drm_overlay_off(struct drm_device *, struct armada_overlay *);
+
+int armada_drm_debugfs_init(struct drm_minor *);
+void armada_drm_debugfs_cleanup(struct drm_minor *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c
new file mode 100644
index 0000000..807fd35
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+/*
+ * Armada510 specific initialization.  There _may_ be two external
+ * clocks - look them up now but don't fail if they're not present.
+ * We may be able to use the internal clocks instead.
+ */
+static int armada510_init(struct armada_private *priv, struct device *dev)
+{
+	priv->extclk[0] = devm_clk_get(dev, "ext_ref_clk_1");
+
+	if (IS_ERR(priv->extclk[0])) {
+		int ret = PTR_ERR(priv->extclk[0]);
+		if (ret == -ENOENT)
+			ret = -EPROBE_DEFER;
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Armada510 specific SCLK register selection: this gets called with
+ * sclk = NULL to test whether the mode is supportable, and again with
+ * sclk != NULL to set the clocks up for that.  The former can return
+ * an error, but the latter is expected not to.
+ *
+ * We currently are pretty rudimentary here, always selecting
+ * EXT_REF_CLK_1 for LCD0 and erroring LCD1.  This needs improvement!
+ */
+static int armada510_compute_crtc_clock(struct armada_crtc *dcrtc,
+	const struct drm_display_mode *mode, uint32_t *sclk)
+{
+	struct armada_private *priv = dcrtc->crtc.dev->dev_private;
+	struct clk *clk = priv->extclk[0];
+	int ret;
+
+	if (dcrtc->num == 1)
+		return -EINVAL;
+
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	if (dcrtc->clk != clk) {
+		ret = clk_prepare_enable(clk);
+		if (ret)
+			return ret;
+		dcrtc->clk = clk;
+	}
+
+	if (sclk) {
+		uint32_t rate, ref, div;
+
+		rate = mode->clock * 1000;
+		ref = clk_round_rate(clk, rate);
+		div = DIV_ROUND_UP(ref, rate);
+		if (div < 1)
+			div = 1;
+
+		clk_set_rate(clk, ref);
+		*sclk = div | SCLK_510_EXTCLK1;
+	}
+
+	return 0;
+}
+
+static const struct armada_ops armada510_ops = {
+	.init = armada510_init,
+	.compute_crtc_clock = armada510_compute_crtc_clock,
+};
+
+static int armada_drm_load(struct drm_device *dev, unsigned long flags)
+{
+	struct armada_private *priv;
+	struct resource *res[ARRAY_SIZE(priv->dcrtc)];
+	struct resource *mem = NULL;
+	int ret, n, i;
+
+	memset(res, 0, sizeof(res));
+
+	for (n = i = 0; ; n++) {
+		struct resource *r = platform_get_resource(dev->platformdev,
+							   IORESOURCE_MEM, n);
+		if (!r)
+			break;
+
+		/* Resources above 64K are graphics memory */
+		if (resource_size(r) > SZ_64K)
+			mem = r;
+		else if (i < ARRAY_SIZE(priv->dcrtc))
+			res[i++] = r;
+		else
+			return -EINVAL;
+	}
+
+	if (!res[0] || !mem)
+		return -ENXIO;
+
+	if (!devm_request_mem_region(dev->dev, mem->start,
+			resource_size(mem), "armada-drm"))
+		return -EBUSY;
+
+	priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		DRM_ERROR("failed to allocate private\n");
+		ret = -ENOMEM;
+		goto err_buf;
+	}
+
+	dev->dev_private = priv;
+
+	priv->ops = &armada510_ops;
+
+	ret = priv->ops->init(priv, dev->dev);
+	if (ret)
+		return ret;
+
+	/* Mode setting support */
+	drm_mode_config_init(dev);
+	dev->mode_config.min_width = 0;
+	dev->mode_config.min_height = 0;
+	dev->mode_config.max_width = 2048;
+	dev->mode_config.max_height = 2048;
+	dev->mode_config.preferred_depth = 24;
+	dev->mode_config.funcs = &armada_drm_mode_config_funcs;
+	drm_mm_init(&priv->linear, mem->start, resource_size(mem));
+
+	/* Create all LCD controllers */
+	for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
+		if (!res[n])
+			break;
+
+		ret = armada_drm_crtc_create(dev, n, res[n]);
+		if (ret)
+			goto err_kms;
+	}
+
+	ret = drm_vblank_init(dev, n);
+	if (ret)
+		goto err_kms;
+
+	ret = drm_irq_install(dev);
+	if (ret)
+		goto err_kms;
+
+	dev->vblank_disable_allowed = 1;
+
+	ret = armada_fbdev_init(dev);
+	if (ret)
+		goto err_irq;
+
+	ret = armada_overlay_create(dev);
+	if (ret)
+		goto err_irq;
+
+	drm_kms_helper_poll_init(dev);
+
+	return 0;
+
+ err_irq:
+	drm_irq_uninstall(dev);
+ err_kms:
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+ err_buf:
+
+	return ret;
+}
+
+static int armada_drm_unload(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+
+	drm_kms_helper_poll_fini(dev);
+	armada_overlay_destroy(dev);
+	armada_fbdev_fini(dev);
+	drm_irq_uninstall(dev);
+	drm_mode_config_cleanup(dev);
+	drm_mm_takedown(&priv->linear);
+	dev->dev_private = NULL;
+
+	return 0;
+}
+
+void armada_drm_vbl_event_add(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	if (list_empty(&evt->node)) {
+		list_add_tail(&evt->node, &dcrtc->vbl_list);
+
+		drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
+	}
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+void armada_drm_vbl_event_remove(struct armada_crtc *dcrtc,
+	struct armada_vbl_event *evt)
+{
+	if (!list_empty(&evt->node)) {
+		list_del_init(&evt->node);
+		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
+	}
+}
+
+void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *dcrtc, struct armada_vbl_event *evt)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dcrtc->irq_lock, flags);
+	armada_drm_vbl_event_remove(dcrtc, evt);
+	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
+}
+
+/* These are called under the vbl_lock. */
+static int armada_drm_enable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_enable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+	return 0;
+}
+
+static void armada_drm_disable_vblank(struct drm_device *dev, int crtc)
+{
+	struct armada_private *priv = dev->dev_private;
+	armada_drm_crtc_disable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA);
+}
+
+static irqreturn_t armada_drm_irq_handler(int irq, void *arg)
+{
+	struct drm_device *dev = arg;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+	uint32_t v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);
+	irqreturn_t handled = IRQ_NONE;
+
+	/*
+	 * This is rediculous - rather than writing bits to clear, we
+	 * have to set the actual status register value.  This is racy.
+	 */
+	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+
+	/* Mask out those interrupts we haven't enabled */
+	v = stat & dcrtc->irq_ena;
+
+	if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) {
+		armada_drm_crtc_irq(dcrtc, stat);
+		handled = IRQ_HANDLED;
+	}
+
+	return handled;
+}
+
+static int armada_drm_irq_postinstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	spin_lock_irq(&dev->vbl_lock);
+	writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ISR);
+	spin_unlock_irq(&dev->vbl_lock);
+
+	return 0;
+}
+
+static void armada_drm_irq_uninstall(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_crtc *dcrtc = priv->dcrtc[0];
+
+	writel(0, dcrtc->base + LCD_SPU_IRQ_ENA);
+}
+
+static struct drm_ioctl_desc armada_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE_PHYS, armada_gem_create_phys_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_MMAP, armada_gem_mmap_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PROP, armada_gem_prop_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_GEM_PWRITE, armada_gem_pwrite_ioctl, DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_PUT_IMAGE, armada_overlay_put_image_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+	DRM_IOCTL_DEF_DRV(ARMADA_OVERLAY_ATTRS, armada_overlay_attrs_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
+};
+
+static const struct file_operations armada_drm_fops = {
+	.owner			= THIS_MODULE,
+	.llseek			= no_llseek,
+	.read			= drm_read,
+	.poll			= drm_poll,
+	.unlocked_ioctl		= drm_ioctl,
+	.mmap			= drm_gem_mmap,
+	.open			= drm_open,
+	.release		= drm_release,
+	.fasync			= drm_fasync,
+};
+
+extern const struct vm_operations_struct armada_gem_vm_ops;
+
+static struct drm_driver armada_drm_driver = {
+	.load			= armada_drm_load,
+	.open			= NULL,
+	.preclose		= NULL,
+	.postclose		= NULL,
+	.lastclose		= NULL,
+	.unload			= armada_drm_unload,
+	.get_vblank_counter	= drm_vblank_count,
+	.enable_vblank		= armada_drm_enable_vblank,
+	.disable_vblank		= armada_drm_disable_vblank,
+	.irq_handler		= armada_drm_irq_handler,
+	.irq_postinstall	= armada_drm_irq_postinstall,
+	.irq_uninstall		= armada_drm_irq_uninstall,
+#ifdef CONFIG_DEBUG_FS
+	.debugfs_init		= armada_drm_debugfs_init,
+	.debugfs_cleanup	= armada_drm_debugfs_cleanup,
+#endif
+	.gem_init_object	= NULL,
+	.gem_free_object	= armada_gem_free_object,
+	.prime_handle_to_fd	= NULL,
+	.prime_fd_to_handle	= NULL,
+	.gem_prime_export	= NULL,
+	.gem_prime_import	= NULL,
+	.dumb_create		= armada_gem_dumb_create,
+	.dumb_map_offset	= armada_gem_dumb_map_offset,
+	.dumb_destroy		= armada_gem_dumb_destroy,
+	.gem_vm_ops		= &armada_gem_vm_ops,
+	.major			= 1,
+	.minor			= 0,
+	.name			= "armada-drm",
+	.desc			= "Armada SoC DRM",
+	.date			= "20120730",
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_HAVE_IRQ,
+	.ioctls			= armada_ioctls,
+	.fops			= &armada_drm_fops,
+};
+
+static int armada_drm_probe(struct platform_device *pdev)
+{
+	return drm_platform_init(&armada_drm_driver, pdev);
+}
+
+static int armada_drm_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&armada_drm_driver, pdev);
+	return 0;
+}
+
+static struct platform_driver armada_drm_platform_driver = {
+	.probe	= armada_drm_probe,
+	.remove	= armada_drm_remove,
+	.driver	= {
+		.owner	= THIS_MODULE,
+		.name	= "armada-drm",
+	},
+};
+
+static int __init armada_drm_init(void)
+{
+	armada_drm_driver.num_ioctls = DRM_ARRAY_SIZE(armada_ioctls);
+	return platform_driver_register(&armada_drm_platform_driver);
+}
+module_init(armada_drm_init);
+
+static void __exit armada_drm_exit(void)
+{
+	platform_driver_unregister(&armada_drm_platform_driver);
+}
+module_exit(armada_drm_exit);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Armada DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:armada-drm");
diff --git a/drivers/gpu/drm/armada/armada_fb.c b/drivers/gpu/drm/armada/armada_fb.c
new file mode 100644
index 0000000..8f72f13
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static void armada_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+
+	drm_framebuffer_cleanup(&dfb->fb);
+	if (dfb->obj)
+		drm_gem_object_unreference_unlocked(&dfb->obj->obj);
+	kfree(dfb);
+}
+
+static int armada_fb_create_handle(struct drm_framebuffer *fb,
+	struct drm_file *dfile, unsigned int *handle)
+{
+	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
+	return drm_gem_handle_create(dfile, &dfb->obj->obj, handle);
+}
+
+static const struct drm_framebuffer_funcs armada_fb_funcs = {
+	.destroy	= armada_fb_destroy,
+	.create_handle	= armada_fb_create_handle,
+};
+
+/*
+ * Supported pixel formats:
+ *		T	R	G	B	BPP
+ * RGB565 		[15:11] [10:5]	[4:0]	16	DRM_FORMAT_RGB565
+ * BGR565		[4:0]	[10:5]	[15:11]	16	DRM_FORMAT_BGR565
+ * RGB1555	15	[14:10]	[9:5]	[4:0]	16	DRM_FORMAT_ARGB1555
+ * BGR1555	15	[4:0]	[9:5]	[14:10]	16	DRM_FORMAT_ABGR1555
+ * RGB888PACK		[23:16]	[15:8]	[7:0]	24	DRM_FORMAT_RGB888
+ * BGR888PACK		[7:0]	[15:8]	[23:16]	24	DRM_FORMAT_BGR888
+ * RGB888UNPACK		[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_XRGB8888
+ * BGR888UNPACK		[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_XBGR8888
+ * RGBA888	[31:24]	[23:16]	[15:8]	[7:0]	32	DRM_FORMAT_ARGB8888
+ * BGRA888	[31:24]	[7:0]	[15:8]	[23:16]	32	DRM_FORMAT_ABGR8888
+ *
+ * We don't currently support (note that the YUV pixel fields
+ * are incorrect, coming from the dovefb driver):
+ *			Y	U	V	BPP
+ * YUV422PACK		[15:8]	[7:4]	[3:0]	16
+ * YVU422PACK		[7:0]	[11:8]	[15:12]	16
+ * YUV422PLANAR		[15:8]	[7:4]	[3:0]	16
+ * YVU422PLANAR		[7:0]	[11:8]	[15:12]	16
+ * YUV420PLANAR		[11:4]	[3:2]	[1:0]	12
+ * YVU420PLANAR		[7:0]	[9:8]	[11:10]	12
+ * PSEUDOCOLOR					8
+ * UYVY422PACK		[11:4]	[15:12]	[3:0]	16
+ */
+int armada_framebuffer_create(struct drm_device *dev,
+	struct armada_framebuffer **dfbp, struct drm_mode_fb_cmd2 *mode,
+	struct armada_gem_object *obj)
+{
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	switch (mode->pixel_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
+	if (!dfb) {
+		DRM_ERROR("failed to allocate Armada fb object\n");
+		return -ENOMEM;
+	}
+
+	ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs);
+	if (ret) {
+		kfree(dfb);
+		return ret;
+	}
+
+	drm_helper_mode_fill_fb_struct(&dfb->fb, mode);
+
+	/*
+	 * Take a reference on our object - the caller is expected
+	 * to drop their reference to it.
+	 */
+	drm_gem_object_reference(&obj->obj);
+	dfb->obj = obj;
+	*dfbp = dfb;
+
+	return 0;
+}
+
+static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
+	struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode)
+{
+	struct armada_gem_object *obj;
+	struct armada_framebuffer *dfb;
+	int ret;
+
+	DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n",
+		mode->width, mode->height, mode->pixel_format,
+		mode->flags, mode->pitches[0], mode->pitches[1],
+		mode->pitches[2]);
+
+	/* We can only handle a single plane at the moment */
+	if (drm_format_num_planes(mode->pixel_format) > 1)
+		return ERR_PTR(-EINVAL);
+
+	obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		return ERR_PTR(-ENOENT);
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, mode, obj);
+	drm_gem_object_unreference_unlocked(&obj->obj);
+
+	if (ret) {
+		DRM_ERROR("failed to initialize framebuffer\n");
+		return ERR_PTR(ret);
+	}
+
+	return &dfb->fb;
+}
+
+static void armada_output_poll_changed(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh)
+		drm_fb_helper_hotplug_event(fbh);
+}
+
+const struct drm_mode_config_funcs armada_drm_mode_config_funcs = {
+	.fb_create		= armada_fb_create,
+	.output_poll_changed	= armada_output_poll_changed,
+};
diff --git a/drivers/gpu/drm/armada/armada_fb.h b/drivers/gpu/drm/armada/armada_fb.h
new file mode 100644
index 0000000..0c78d2b
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fb.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_FB_H
+#define ARMADA_FB_H
+
+struct armada_framebuffer {
+	struct drm_framebuffer	fb;
+	struct armada_gem_object	*obj;
+};
+#define drm_fb_to_armada_fb(dfb) container_of(dfb, struct armada_framebuffer, fb)
+#define drm_fb_obj(fb) drm_fb_to_armada_fb(fb)->obj
+
+int armada_framebuffer_create(struct drm_device *, struct armada_framebuffer **,
+	struct drm_mode_fb_cmd2 *, struct armada_gem_object *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_fbdev.c b/drivers/gpu/drm/armada/armada_fbdev.c
new file mode 100644
index 0000000..bfacdd0
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_fbdev.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Written from the i915 driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_fb.h"
+#include "armada_gem.h"
+
+static /*const*/ struct fb_ops armada_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_check_var	= drm_fb_helper_check_var,
+	.fb_set_par	= drm_fb_helper_set_par,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_pan_display	= drm_fb_helper_pan_display,
+	.fb_blank	= drm_fb_helper_blank,
+	.fb_setcmap	= drm_fb_helper_setcmap,
+	.fb_debug_enter	= drm_fb_helper_debug_enter,
+	.fb_debug_leave	= drm_fb_helper_debug_leave,
+};
+
+static int armada_fb_create(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	struct drm_device *dev = fbh->dev;
+	struct drm_mode_fb_cmd2 mode;
+	struct armada_framebuffer *dfb;
+	struct armada_gem_object *obj;
+	struct fb_info *info;
+	int size, ret;
+	void *ptr;
+
+	memset(&mode, 0, sizeof(mode));
+	mode.width = sizes->surface_width;
+	mode.height = sizes->surface_height;
+	mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp);
+	mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+					sizes->surface_depth);
+
+	size = ALIGN(mode.pitches[0] * mode.height, PAGE_SIZE);
+	obj = armada_gem_alloc_private_object(dev, size);
+	if (!obj) {
+		DRM_ERROR("failed to allocate fb memory\n");
+		return -ENOMEM;
+	}
+
+	ret = armada_gem_linear_back(dev, obj);
+	if (ret) {
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return ret;
+	}
+
+	ptr = armada_gem_map_object(dev, obj);
+	if (!ptr) {
+		drm_gem_object_unreference_unlocked(&obj->obj);
+		return -ENOMEM;
+	}
+
+	ret = armada_framebuffer_create(dev, &dfb, &mode, obj);
+	if (ret)
+		goto err_fbcreate;
+
+	mutex_lock(&dev->struct_mutex);
+
+	info = framebuffer_alloc(0, dev->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto err_fballoc;
+	}
+
+	ret = fb_alloc_cmap(&info->cmap, 256, 0);
+	if (ret) {
+		ret = -ENOMEM;
+		goto err_fbcmap;
+	}
+
+	strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id));
+	info->par = fbh;
+	info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+	info->fbops = &armada_fb_ops;
+	info->fix.smem_start = obj->phys_addr;
+	info->fix.smem_len = size;
+	info->screen_size = size;
+	info->screen_base = ptr;
+	fbh->fb = &dfb->fb;
+	fbh->fbdev = info;
+	drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth);
+	drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height);
+
+	DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08x\n",
+		dfb->fb.width, dfb->fb.height,
+		dfb->fb.bits_per_pixel, obj->phys_addr);
+
+	/* Reference is now held by the framebuffer object */
+	drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+
+	return 0;
+
+ err_fbcmap:
+	framebuffer_release(info);
+ err_fballoc:
+	mutex_unlock(&dev->struct_mutex);
+	dfb->fb.funcs->destroy(&dfb->fb);
+ err_fbcreate:
+	drm_gem_object_unreference_unlocked(&obj->obj);
+	return ret;
+}
+
+static void armada_fb_destroy(struct drm_fb_helper *fbh)
+{
+	struct fb_info *info = fbh->fbdev;
+
+	if (info) {
+		unregister_framebuffer(info);
+		if (info->cmap.len)
+			fb_dealloc_cmap(&info->cmap);
+		framebuffer_release(info);
+	}
+
+	if (fbh->fb)
+		fbh->fb->funcs->destroy(fbh->fb);
+
+	drm_fb_helper_fini(fbh);
+}
+
+static int armada_fb_probe(struct drm_fb_helper *fbh,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	int ret = 0;
+
+	if (!fbh->fb) {
+		ret = armada_fb_create(fbh, sizes);
+		if (ret == 0)
+			ret = 1;
+	}
+	return ret;
+}
+
+static struct drm_fb_helper_funcs armada_fb_helper_funcs = {
+	.gamma_set	= armada_drm_crtc_gamma_set,
+	.gamma_get	= armada_drm_crtc_gamma_get,
+	.fb_probe	= armada_fb_probe,
+};
+
+int armada_fbdev_init(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh;
+	int ret;
+
+	fbh = kzalloc(sizeof(*fbh), GFP_KERNEL);
+	if (!fbh)
+		return -ENOMEM;
+
+	priv->fbdev = fbh;
+
+	fbh->funcs = &armada_fb_helper_funcs;
+
+	ret = drm_fb_helper_init(dev, fbh, 1, 1);
+	if (ret) {
+		DRM_ERROR("failed to initialize drm fb helper\n");
+		goto err_fb_helper;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(fbh);
+	if (ret) {
+		DRM_ERROR("failed to add fb connectors\n");
+		goto err_fb_setup;
+	}
+
+	ret = drm_fb_helper_initial_config(fbh, 32);
+	if (ret) {
+		DRM_ERROR("failed to set initial config\n");
+		goto err_fb_setup;
+	}
+
+	return 0;
+ err_fb_setup:
+	drm_fb_helper_fini(fbh);
+ err_fb_helper:
+	kfree(fbh);
+	priv->fbdev = NULL;
+	return ret;
+}
+
+void armada_fbdev_fini(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct drm_fb_helper *fbh = priv->fbdev;
+
+	if (fbh) {
+		armada_fb_destroy(fbh);
+		kfree(fbh);
+		priv->fbdev = NULL;
+	}
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c
new file mode 100644
index 0000000..c4e7d5f
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/shmem_fs.h>
+#include <drm/drmP.h>
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+static int armada_gem_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct armada_gem_object *obj = drm_to_armada_gem(vma->vm_private_data);
+	unsigned long addr = (unsigned long)vmf->virtual_address;
+	unsigned long pfn = obj->phys_addr >> PAGE_SHIFT;
+	int ret;
+
+	pfn += (addr - vma->vm_start) >> PAGE_SHIFT;
+	ret = vm_insert_pfn(vma, addr, pfn);
+
+	switch (ret) {
+	case -EIO:
+	case -EAGAIN:
+		set_need_resched();
+	case 0:
+	case -ERESTARTSYS:
+	case -EINTR:
+		return VM_FAULT_NOPAGE;
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	default:
+		return VM_FAULT_SIGBUS;
+	}
+}
+
+const struct vm_operations_struct armada_gem_vm_ops = {
+	.fault	= armada_gem_vm_fault,
+	.open	= drm_gem_vm_open,
+	.close	= drm_gem_vm_close,
+};
+
+static size_t roundup_gem_size(size_t size)
+{
+	return roundup(size, PAGE_SIZE);
+}
+
+void armada_gem_free_object(struct drm_gem_object *obj)
+{
+	struct armada_gem_object *dobj = drm_to_armada_gem(obj);
+
+	DRM_DEBUG_DRIVER("release obj %p\n", dobj);
+
+	if (dobj->linear)
+		drm_mm_put_block(dobj->linear);
+
+	if (dobj->obj.map_list.map)
+		drm_gem_free_mmap_offset(&dobj->obj);
+
+	if (dobj->page) {
+		unsigned int order = get_order(dobj->obj.size);
+		__free_pages(dobj->page, order);
+		/* avoid the iounmap below - these aren't ioremap'd */
+		dobj->addr = NULL;
+	}
+
+	if (dobj->addr)
+		iounmap(dobj->addr);
+
+	drm_gem_object_release(&dobj->obj);
+
+	kfree(dobj);
+}
+
+int
+armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj)
+{
+	struct armada_private *priv = dev->dev_private;
+	size_t size = obj->obj.size;
+
+	if (obj->page || obj->linear)
+		return 0;
+
+	/*
+	 * If it is a small allocation (typically cursor)
+	 * try to get it from the system
+	 */
+	if (size < 1048576) {
+		unsigned int order = get_order(size);
+		struct page *p = alloc_pages(GFP_KERNEL, order);
+
+		if (p) {
+			unsigned sz = (size + 31) & ~31;
+			uintptr_t ptr;
+
+			obj->phys_addr = page_to_phys(p);
+			obj->dev_addr = obj->phys_addr;
+			obj->page = p;
+			obj->addr = page_address(p);
+
+			memset(obj->addr, 0, PAGE_ALIGN(size));
+
+			/* FIXME: Hack around dirty cache */
+			ptr = (uintptr_t)obj->addr;
+			while (sz) {
+				asm volatile("mcr p15, 0, %0, c7, c14, 1" : : "r" (ptr));
+				ptr += L1_CACHE_BYTES;
+				sz -= L1_CACHE_BYTES;
+			}
+			dsb();
+		}
+	}
+
+	/* Otherwise, grab it from our linear allocation */
+	if (!obj->page) {
+		struct drm_mm_node *free;
+		unsigned align = min_t(unsigned, size, SZ_2M);
+		void __iomem *ptr;
+
+		free = drm_mm_search_free(&priv->linear, size, align, 0);
+		if (free)
+			obj->linear = drm_mm_get_block(free, size, align);
+		if (!obj->linear)
+			return -ENOSPC;
+
+		/* Ensure that the memory we're returning is cleared. */
+		ptr = ioremap_wc(obj->linear->start, size);
+		if (!ptr) {
+			drm_mm_put_block(obj->linear);
+			obj->linear = NULL;
+			return -ENOMEM;
+		}
+
+		memset_io(ptr, 0, size);
+		iounmap(ptr);
+
+		obj->phys_addr = obj->linear->start;
+		obj->dev_addr = obj->linear->start;
+	}
+
+	DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n",
+			 obj, obj->phys_addr, obj->dev_addr);
+
+	return 0;
+}
+
+void *
+armada_gem_map_object(struct drm_device *dev, struct armada_gem_object *dobj)
+{
+	if (!dobj->addr)
+		dobj->addr = ioremap_wc(dobj->phys_addr, dobj->obj.size);
+	return dobj->addr;
+}
+
+struct armada_gem_object *
+armada_gem_alloc_private_object(struct drm_device *dev, size_t size)
+{
+	struct armada_gem_object *obj;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_private_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	DRM_DEBUG_DRIVER("alloc private obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+struct armada_gem_object *armada_gem_alloc_object(struct drm_device *dev,
+	size_t size)
+{
+	struct armada_gem_object *obj;
+	struct address_space *mapping;
+
+	size = roundup_gem_size(size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_object_init(dev, &obj->obj, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	mapping = obj->obj.filp->f_path.dentry->d_inode->i_mapping;
+	mapping_set_gfp_mask(mapping, GFP_HIGHUSER | __GFP_RECLAIMABLE);
+
+	DRM_DEBUG_DRIVER("alloc obj %p size %zu\n", obj, size);
+
+	return obj;
+}
+
+/* Dumb alloc support */
+int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
+	struct drm_mode_create_dumb *args)
+{
+	struct armada_gem_object *dobj;
+	u32 handle;
+	size_t size;
+	int ret;
+
+	args->pitch = armada_pitch(args->width, args->bpp);
+	args->size = size = args->pitch * args->height;
+
+	dobj = armada_gem_alloc_private_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = armada_gem_linear_back(dev, dobj);
+	if (ret)
+		goto err;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle, uint64_t *offset)
+{
+	struct armada_gem_object *obj;
+	int ret = 0;
+
+	mutex_lock(&dev->struct_mutex);
+	obj = armada_gem_object_lookup(dev, file, handle);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object\n");
+		ret = -EINVAL;
+		goto err_unlock;
+	}
+
+	if (!obj->obj.map_list.map)
+		ret = drm_gem_create_mmap_offset(&obj->obj);
+
+	if (ret == 0) {
+		*offset = (u64)obj->obj.map_list.hash.key << PAGE_SHIFT;
+		DRM_DEBUG_DRIVER("handle %#x offset %llx\n", handle, *offset);
+	}
+
+	drm_gem_object_unreference(&obj->obj);
+ err_unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev,
+	uint32_t handle)
+{
+	return drm_gem_handle_delete(file, handle);
+}
+
+/* Private driver gem ioctls */
+int armada_gem_create_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create *args = data;
+	struct armada_gem_object *dobj;
+	size_t size;
+	u32 handle;
+	int ret;
+
+	if (args->size == 0)
+		return -ENOMEM;
+
+	size = args->size;
+
+	dobj = armada_gem_alloc_object(dev, size);
+	if (dobj == NULL)
+		return -ENOMEM;
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret)
+		goto err;
+
+	args->handle = handle;
+
+	/* drop reference from allocate - handle holds it now */
+	DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
+ err:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
+
+int armada_gem_create_phys_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_create_phys *arg = data;
+	struct armada_gem_object *dobj;
+	u32 handle;
+	int ret;
+
+	dobj = armada_gem_alloc_private_object(dev, arg->size);
+	if (dobj) {
+		dobj->phys_addr = arg->phys;
+		dobj->dev_addr = arg->phys;
+	}
+
+	ret = drm_gem_handle_create(file, &dobj->obj, &handle);
+	if (ret) {
+		drm_gem_object_release(&dobj->obj);
+		return ret;
+	}
+
+	/* drop reference from allocate - handle holds it now */
+	drm_gem_object_unreference(&dobj->obj);
+
+	arg->handle = handle;
+
+	return 0;
+}
+
+/* Map a shmem-backed object into process memory space */
+int armada_gem_mmap_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_mmap *args = data;
+	struct armada_gem_object *dobj;
+	unsigned long addr;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	if (!dobj->obj.filp) {
+		drm_gem_object_unreference(&dobj->obj);
+		return -EINVAL;
+	}
+
+	addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE,
+	               MAP_SHARED, args->offset);
+	drm_gem_object_unreference(&dobj->obj);
+	if (IS_ERR_VALUE(addr))
+		return addr;
+
+	args->addr = addr;
+
+	return 0;
+}
+
+int armada_gem_prop_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_prop *arg = data;
+	struct armada_gem_object *dobj;
+	int ret;
+
+	ret = mutex_lock_interruptible(&dev->struct_mutex);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, arg->handle);
+	if (dobj == NULL) {
+		ret = -ENOENT;
+		goto unlock;
+	}
+
+	arg->phys = dobj->phys_addr;
+	ret = 0;
+	drm_gem_object_unreference(&dobj->obj);
+
+ unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
+int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_gem_pwrite *args = data;
+	struct armada_gem_object *dobj;
+	char __user *ptr;
+	int ret;
+
+	DRM_DEBUG_DRIVER("handle %u off %u size %u ptr 0x%llx\n",
+		args->handle, args->offset, args->size, args->ptr);
+
+	if (args->size == 0)
+		return 0;
+
+	ptr = (char __user *)(uintptr_t)args->ptr;
+
+	if (!access_ok(VERIFY_READ, ptr, args->size))
+		return -EFAULT;
+
+	ret = fault_in_multipages_readable(ptr, args->size);
+	if (ret)
+		return ret;
+
+	dobj = armada_gem_object_lookup(dev, file, args->handle);
+	if (dobj == NULL)
+		return -ENOENT;
+
+	/* Must be a kernel-mapped object */
+	if (!dobj->addr)
+		return -EINVAL;
+
+	if (args->offset > dobj->obj.size ||
+	    args->size > dobj->obj.size - args->offset) {
+		DRM_ERROR("invalid size: object size %u\n", dobj->obj.size);
+		ret = -EINVAL;
+		goto unref;
+	}
+
+	if (copy_from_user(dobj->addr + args->offset, ptr, args->size)) {
+		ret = -EFAULT;
+	} else if (dobj->update) {
+		dobj->update(dobj->update_data);
+		ret = 0;
+	}
+
+  unref:
+	drm_gem_object_unreference_unlocked(&dobj->obj);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_gem.h b/drivers/gpu/drm/armada/armada_gem.h
new file mode 100644
index 0000000..87cd94b5
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_gem.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_GEM_H
+#define ARMADA_GEM_H
+
+/* GEM */
+struct armada_gem_object {
+	struct drm_gem_object	obj;
+	void			*addr;
+	struct drm_mm_node	*linear;
+	phys_addr_t		phys_addr;
+	resource_size_t		dev_addr;
+	struct page		*page;
+	void			(*update)(void *);
+	void			*update_data;
+};
+
+#define drm_to_armada_gem(o) container_of(o, struct armada_gem_object, obj)
+
+void armada_gem_free_object(struct drm_gem_object *);
+int armada_gem_linear_back(struct drm_device *, struct armada_gem_object *);
+void *armada_gem_map_object(struct drm_device *, struct armada_gem_object *);
+struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *, size_t);
+int armada_gem_dumb_create(struct drm_file *, struct drm_device *,
+	struct drm_mode_create_dumb *);
+int armada_gem_dumb_map_offset(struct drm_file *, struct drm_device *,
+	uint32_t, uint64_t *);
+int armada_gem_dumb_destroy(struct drm_file *, struct drm_device *,
+	uint32_t);
+
+static inline struct armada_gem_object *armada_gem_object_lookup(
+	struct drm_device *dev, struct drm_file *dfile, unsigned handle)
+{
+	struct drm_gem_object *obj = drm_gem_object_lookup(dev, dfile, handle);
+
+	return obj ? drm_to_armada_gem(obj) : NULL;
+}
+#endif
diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h
new file mode 100644
index 0000000..58d2a20
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_hw.h
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_HW_H
+#define ARMADA_HW_H
+
+/*
+ * Note: the following registers are written from IRQ context:
+ *  LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
+ *  LCD_SPU_DMA_START_ADDR_[YUV][01], LCD_SPU_DMA_PITCH_YC,
+ *  LCD_SPU_DMA_PITCH_UV, LCD_SPU_DMA_OVSA_HPXL_VLN,
+ *  LCD_SPU_DMA_HPXL_VLN, LCD_SPU_DZM_HPXL_VLN, LCD_SPU_DMA_CTRL0
+ */
+enum {
+	LCD_SPU_ADV_REG			= 0x0084,
+	LCD_SPU_DMA_START_ADDR_Y0	= 0x00c0,
+	LCD_SPU_DMA_START_ADDR_U0	= 0x00c4,
+	LCD_SPU_DMA_START_ADDR_V0	= 0x00c8,
+	LCD_CFG_DMA_START_ADDR_0	= 0x00cc,
+	LCD_SPU_DMA_START_ADDR_Y1	= 0x00d0,
+	LCD_SPU_DMA_START_ADDR_U1	= 0x00d4,
+	LCD_SPU_DMA_START_ADDR_V1	= 0x00d8,
+	LCD_CFG_DMA_START_ADDR_1	= 0x00dc,
+	LCD_SPU_DMA_PITCH_YC		= 0x00e0,
+	LCD_SPU_DMA_PITCH_UV		= 0x00e4,
+	LCD_SPU_DMA_OVSA_HPXL_VLN	= 0x00e8,
+	LCD_SPU_DMA_HPXL_VLN		= 0x00ec,
+	LCD_SPU_DZM_HPXL_VLN		= 0x00f0,
+	LCD_CFG_GRA_START_ADDR0		= 0x00f4,
+	LCD_CFG_GRA_START_ADDR1		= 0x00f8,
+	LCD_CFG_GRA_PITCH		= 0x00fc,
+	LCD_SPU_GRA_OVSA_HPXL_VLN	= 0x0100,
+	LCD_SPU_GRA_HPXL_VLN		= 0x0104,
+	LCD_SPU_GZM_HPXL_VLN		= 0x0108,
+	LCD_SPU_HWC_OVSA_HPXL_VLN	= 0x010c,
+	LCD_SPU_HWC_HPXL_VLN		= 0x0110,
+	LCD_SPUT_V_H_TOTAL		= 0x0114,
+	LCD_SPU_V_H_ACTIVE		= 0x0118,
+	LCD_SPU_H_PORCH			= 0x011c,
+	LCD_SPU_V_PORCH			= 0x0120,
+	LCD_SPU_BLANKCOLOR		= 0x0124,
+	LCD_SPU_ALPHA_COLOR1		= 0x0128,
+	LCD_SPU_ALPHA_COLOR2		= 0x012c,
+	LCD_SPU_COLORKEY_Y		= 0x0130,
+	LCD_SPU_COLORKEY_U		= 0x0134,
+	LCD_SPU_COLORKEY_V		= 0x0138,
+	LCD_CFG_RDREG4F			= 0x013c,
+	LCD_SPU_SPI_RXDATA		= 0x0140,
+	LCD_SPU_ISA_RSDATA		= 0x0144,
+	LCD_SPU_HWC_RDDAT		= 0x0158,
+	LCD_SPU_GAMMA_RDDAT		= 0x015c,
+	LCD_SPU_PALETTE_RDDAT		= 0x0160,
+	LCD_SPU_IOPAD_IN		= 0x0178,
+	LCD_CFG_RDREG5F			= 0x017c,
+	LCD_SPU_SPI_CTRL		= 0x0180,
+	LCD_SPU_SPI_TXDATA		= 0x0184,
+	LCD_SPU_SMPN_CTRL		= 0x0188,
+	LCD_SPU_DMA_CTRL0		= 0x0190,
+	LCD_SPU_DMA_CTRL1		= 0x0194,
+	LCD_SPU_SRAM_CTRL		= 0x0198,
+	LCD_SPU_SRAM_WRDAT		= 0x019c,
+	LCD_SPU_SRAM_PARA0		= 0x01a0,
+	LCD_SPU_SRAM_PARA1		= 0x01a4,
+	LCD_CFG_SCLK_DIV		= 0x01a8,
+	LCD_SPU_CONTRAST		= 0x01ac,
+	LCD_SPU_SATURATION		= 0x01b0,
+	LCD_SPU_CBSH_HUE		= 0x01b4,
+	LCD_SPU_DUMB_CTRL		= 0x01b8,
+	LCD_SPU_IOPAD_CONTROL		= 0x01bc,
+	LCD_SPU_IRQ_ENA			= 0x01c0,
+	LCD_SPU_IRQ_ISR			= 0x01c4,
+};
+
+/* For LCD_SPU_ADV_REG */
+enum {
+	ADV_VSYNC_L_OFF	= 0xfff << 20,
+	ADV_GRACOLORKEY	= 1 << 19,
+	ADV_VIDCOLORKEY	= 1 << 18,
+	ADV_HWC32BLEND	= 1 << 15,
+	ADV_HWC32ARGB	= 1 << 14,
+	ADV_HWC32ENABLE	= 1 << 13,
+	ADV_VSYNCOFFEN	= 1 << 12,
+	ADV_VSYNC_H_OFF	= 0xfff << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL0 */
+enum {
+	CFG_NOBLENDING	= 1 << 31,
+	CFG_GAMMA_ENA	= 1 << 30,
+	CFG_CBSH_ENA	= 1 << 29,
+	CFG_PALETTE_ENA	= 1 << 28,
+	CFG_ARBFAST_ENA	= 1 << 27,
+	CFG_HWC_1BITMOD	= 1 << 26,
+	CFG_HWC_1BITENA	= 1 << 25,
+	CFG_HWC_ENA	= 1 << 24,
+	CFG_DMAFORMAT	= 0xf << 20,
+	CFG_DMA_565	= 0x0 << 20,
+	CFG_DMA_1555	= 0x1 << 20,
+	CFG_DMA_888PACK	= 0x2 << 20,
+	CFG_DMA_X888	= 0x3 << 20,
+	CFG_DMA_8888	= 0x4 << 20,
+	CFG_DMA_422PACK	= 0x5 << 20,
+	CFG_DMA_422	= 0x6 << 20,
+	CFG_DMA_420	= 0x7 << 20,
+	CFG_DMA_PSEUDO4	= 0x9 << 20,
+	CFG_DMA_PSEUDO8	= 0xa << 20,
+	CFG_GRAFORMAT	= 0xf << 16,
+	CFG_GRA_565	= 0x0 << 16,
+	CFG_GRA_1555	= 0x1 << 16,
+	CFG_GRA_888PACK	= 0x2 << 16,
+	CFG_GRA_X888	= 0x3 << 16,
+	CFG_GRA_8888	= 0x4 << 16,
+	CFG_GRA_422PACK	= 0x5 << 16,
+	CFG_GRA_422	= 0x6 << 16,
+	CFG_GRA_420	= 0x7 << 16,
+	CFG_GRA_PSEUDO4	= 0x9 << 16,
+	CFG_GRA_PSEUDO8	= 0xa << 16,
+	CFG_GRA_FTOGGLE	= 1 << 15,
+	CFG_GRA_HSMOOTH	= 1 << 14,
+	CFG_GRA_TSTMODE	= 1 << 13,
+	CFG_GRA_SWAPRB	= 1 << 12,
+	CFG_GRA_SWAPUV	= 1 << 11,
+	CFG_GRA_SWAPYU	= 1 << 10,
+	CFG_YUV2RGB_GRA	= 1 << 9,
+	CFG_GRA_ENA	= 1 << 8,
+	CFG_DMA_FTOGGLE	= 1 << 7,
+	CFG_DMA_HSMOOTH	= 1 << 6,
+	CFG_DMA_TSTMODE	= 1 << 5,
+	CFG_DMA_SWAPRB	= 1 << 4,
+	CFG_DMA_SWAPUV	= 1 << 3,
+	CFG_DMA_SWAPYU	= 1 << 2,
+	CFG_YUV2RGB_DMA	= 1 << 1,
+	CFG_DMA_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_DMA_CTRL1 */
+enum {
+	CFG_FRAME_TRIG	= 1 << 31,
+	CFG_VSYNC_INV	= 1 << 27,
+	CFG_CKMODE_MASK	= 0x7 << 24,
+	CFG_CKMODE_DIS	= 0x0 << 24,
+	CFG_CKMODE_Y	= 0x1 << 24,
+	CFG_CKMODE_U	= 0x2 << 24,
+	CFG_CKMODE_RGB	= 0x3 << 24,
+	CFG_CKMODE_V	= 0x4 << 24,
+	CFG_CKMODE_R	= 0x5 << 24,
+	CFG_CKMODE_G	= 0x6 << 24,
+	CFG_CKMODE_B	= 0x7 << 24,
+	CFG_CARRY	= 1 << 23,
+	CFG_GATED_CLK	= 1 << 21,
+	CFG_PWRDN_ENA	= 1 << 20,
+	CFG_DSCALE_MASK	= 0x3 << 18,
+	CFG_DSCALE_NONE	= 0x0 << 18,
+	CFG_DSCALE_HALF	= 0x1 << 18,
+	CFG_DSCALE_QUAR = 0x2 << 18,
+	CFG_ALPHAM_MASK	= 0x3 << 16,
+	CFG_ALPHAM_VIDEO= 0x0 << 16,
+	CFG_ALPHAM_GRA	= 0x1 << 16,
+	CFG_ALPHAM_CFG	= 0x2 << 16,
+	CFG_ALPHA_MASK	= 0xff << 8,
+	CFG_PIXCMD_MASK	= 0xff,
+};
+
+/* For LCD_SPU_SRAM_CTRL */
+enum {
+	SRAM_READ	= 0 << 14,
+	SRAM_WRITE	= 2 << 14,
+	SRAM_INIT	= 3 << 14,
+	SRAM_HWC32_RAMR	= 0xc << 8,
+	SRAM_HWC32_RAMG	= 0xd << 8,
+	SRAM_HWC32_RAMB	= 0xe << 8,
+	SRAM_HWC32_TRAN	= 0xf << 8,
+	SRAM_HWC	= 0xf << 8,
+};
+
+/* For LCD_SPU_SRAM_PARA1 */
+enum {
+	CFG_CSB_256x32	= 1 << 15,
+	CFG_CSB_256x24	= 1 << 14,
+	CFG_CSB_256x8	= 1 << 13,
+	CFG_PDWN256x32	= 1 << 7,
+	CFG_PDWN256x24	= 1 << 6,
+	CFG_PDWN256x8	= 1 << 5,
+	CFG_PDWN32x32	= 1 << 3,
+	CFG_PDWN16x66	= 1 << 2,
+	CFG_PDWN32x66	= 1 << 1,
+	CFG_PDWN64x66	= 1 << 0,
+};
+
+/* For LCD_CFG_SCLK_DIV */
+enum {
+	SCLK_510_AXI		= 0x0 << 30,	/* Armada 510 */
+	SCLK_510_EXTCLK0	= 0x1 << 30,	/* Armada 510 */
+	SCLK_510_PLL		= 0x2 << 30,	/* Armada 510 */
+	SCLK_510_EXTCLK1	= 0x3 << 30,	/* Armada 510 */
+	SCLK_DIV_CHANGE		= 1 << 29,
+	SCLK_16X_AHB		= 0x0 << 28,	/* Armada 16x */
+	SCLK_16X_PCLK		= 0x1 << 28,	/* Armada 16x */
+	SCLK_16X_AXI		= 0x4 << 28,	/* Armada 16x */
+	SCLK_16X_PLL		= 0x8 << 28,	/* Armada 16x */
+};
+
+/* For LCD_SPU_DUMB_CTRL */
+enum {
+	DUMB16_RGB565_0	= 0x0 << 28,
+	DUMB16_RGB565_1	= 0x1 << 28,
+	DUMB18_RGB666_0	= 0x2 << 28,
+	DUMB18_RGB666_1	= 0x3 << 28,
+	DUMB12_RGB444_0	= 0x4 << 28,
+	DUMB12_RGB444_1	= 0x5 << 28,
+	DUMB24_RGB888_0	= 0x6 << 28,
+	DUMB_BLANK	= 0x7 << 28,
+	DUMB_MASK	= 0xf << 28,
+	CFG_BIAS_OUT	= 1 << 8,
+	CFG_REV_RGB	= 1 << 7,
+	CFG_INV_CBLANK	= 1 << 6,
+	CFG_INV_CSYNC	= 1 << 5,
+	CFG_INV_HENA	= 1 << 4,	/* Normally active high */
+	CFG_INV_VSYNC	= 1 << 3,	/* Normally active high */
+	CFG_INV_HSYNC	= 1 << 2,	/* Normally active high */
+	CFG_INV_PCLK	= 1 << 1,
+	CFG_DUMB_ENA	= 1 << 0,
+};
+
+/* For LCD_SPU_IOPAD_CONTROL */
+enum {
+	CFG_VSCALE_LN_EN	= 3 << 18,
+	CFG_GRA_VM_ENA		= 1 << 15,
+	CFG_DMA_VM_ENA		= 1 << 13,
+	CFG_CMD_VM_ENA		= 1 << 11,
+	CFG_CSC_MASK		= 3 << 8,
+	CFG_CSC_CCIR709		= 1 << 9,
+	CFG_CSC_PROF		= 1 << 8,
+	CFG_IOPAD_MASK		= 0xf << 0,
+	CFG_IOPAD_DUMB24	= 0x0 << 0,
+	CFG_IOPAD_DUMB18SPI	= 0x1 << 0,
+	CFG_IOPAD_DUMB18GPIO	= 0x2 << 0,
+	CFG_IOPAD_DUMB16SPI	= 0x3 << 0,
+	CFG_IOPAD_DUMB16GPIO	= 0x4 << 0,
+	CFG_IOPAD_DUMB12GPIO	= 0x5 << 0,
+	CFG_IOPAD_SMART18	= 0x6 << 0,
+	CFG_IOPAD_SMART16	= 0x7 << 0,
+	CFG_IOPAD_SMART8	= 0x8 << 0,
+};
+
+#define IOPAD_DUMB24                0x0
+
+/* For LCD_SPU_IRQ_ENA */
+enum {
+	DMA_FRAME_IRQ0_ENA	= 1 << 31,
+	DMA_FRAME_IRQ1_ENA	= 1 << 30,
+	DMA_FRAME_IRQ_ENA	= DMA_FRAME_IRQ0_ENA | DMA_FRAME_IRQ1_ENA,
+	DMA_FF_UNDERFLOW_ENA	= 1 << 29,
+	GRA_FRAME_IRQ0_ENA	= 1 << 27,
+	GRA_FRAME_IRQ1_ENA	= 1 << 26,
+	GRA_FRAME_IRQ_ENA	= GRA_FRAME_IRQ0_ENA | GRA_FRAME_IRQ1_ENA,
+	GRA_FF_UNDERFLOW_ENA	= 1 << 25,
+	VSYNC_IRQ_ENA		= 1 << 23,
+	DUMB_FRAMEDONE_ENA	= 1 << 22,
+	TWC_FRAMEDONE_ENA	= 1 << 21,
+	HWC_FRAMEDONE_ENA	= 1 << 20,
+	SLV_IRQ_ENA		= 1 << 19,
+	SPI_IRQ_ENA		= 1 << 18,
+	PWRDN_IRQ_ENA		= 1 << 17,
+	ERR_IRQ_ENA		= 1 << 16,
+	CLEAN_SPU_IRQ_ISR	= 0xffff,
+};
+
+/* For LCD_SPU_IRQ_ISR */
+enum {
+	DMA_FRAME_IRQ0		= 1 << 31,
+	DMA_FRAME_IRQ1		= 1 << 30,
+	DMA_FRAME_IRQ		= DMA_FRAME_IRQ0 | DMA_FRAME_IRQ1,
+	DMA_FF_UNDERFLOW	= 1 << 29,
+	GRA_FRAME_IRQ0		= 1 << 27,
+	GRA_FRAME_IRQ1		= 1 << 26,
+	GRA_FRAME_IRQ		= GRA_FRAME_IRQ0 | GRA_FRAME_IRQ1,
+	GRA_FF_UNDERFLOW	= 1 << 25,
+	VSYNC_IRQ		= 1 << 23,
+	DUMB_FRAMEDONE		= 1 << 22,
+	TWC_FRAMEDONE		= 1 << 21,
+	HWC_FRAMEDONE		= 1 << 20,
+	SLV_IRQ			= 1 << 19,
+	SPI_IRQ			= 1 << 18,
+	PWRDN_IRQ		= 1 << 17,
+	ERR_IRQ			= 1 << 16,
+	DMA_FRAME_IRQ0_LEVEL	= 1 << 15,
+	DMA_FRAME_IRQ1_LEVEL	= 1 << 14,
+	DMA_FRAME_CNT_ISR	= 3 << 12,
+	GRA_FRAME_IRQ0_LEVEL	= 1 << 11,
+	GRA_FRAME_IRQ1_LEVEL	= 1 << 10,
+	GRA_FRAME_CNT_ISR	= 3 << 8,
+	VSYNC_IRQ_LEVEL		= 1 << 7,
+	DUMB_FRAMEDONE_LEVEL	= 1 << 6,
+	TWC_FRAMEDONE_LEVEL	= 1 << 5,
+	HWC_FRAMEDONE_LEVEL	= 1 << 4,
+	SLV_FF_EMPTY		= 1 << 3,
+	DMA_FF_ALLEMPTY		= 1 << 2,
+	GRA_FF_ALLEMPTY		= 1 << 1,
+	PWRDN_IRQ_LEVEL		= 1 << 0,
+};
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctl.h b/drivers/gpu/drm/armada/armada_ioctl.h
new file mode 100644
index 0000000..c99cc5a
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctl.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  With inspiration from the i915 driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_ARMADA_IOCTL_H
+#define DRM_ARMADA_IOCTL_H
+
+#define DRM_ARMADA_GEM_CREATE		0x00
+#define DRM_ARMADA_GEM_CREATE_PHYS	0x01
+#define DRM_ARMADA_GEM_MMAP		0x02
+#define DRM_ARMADA_GEM_PWRITE		0x03
+#define DRM_ARMADA_GEM_PROP		0x04
+#define DRM_ARMADA_OVERLAY_PUT_IMAGE	0x06
+#define DRM_ARMADA_OVERLAY_ATTRS		0x07
+
+#define ARMADA_IOCTL(dir,name,str) \
+	DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
+
+struct drm_armada_gem_create {
+	uint32_t handle;
+	uint32_t size;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE \
+	ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
+
+struct drm_armada_gem_create_phys {
+	uint32_t size;
+	uint32_t handle;
+	uint64_t phys;
+};
+#define DRM_IOCTL_ARMADA_GEM_CREATE_PHYS \
+	ARMADA_IOCTL(IOWR, GEM_CREATE_PHYS, gem_create_phys)
+
+struct drm_armada_gem_mmap {
+	uint32_t handle;
+	uint32_t pad;
+	uint64_t offset;
+	uint64_t size;
+	uint64_t addr;
+};
+#define DRM_IOCTL_ARMADA_GEM_MMAP \
+	ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
+
+struct drm_armada_gem_pwrite {
+	uint64_t ptr;
+	uint32_t handle;
+	uint32_t offset;
+	uint32_t size;
+};
+#define DRM_IOCTL_ARMADA_GEM_PWRITE \
+	ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
+
+struct drm_armada_gem_prop {
+	uint64_t phys;
+	uint32_t handle;
+};
+#define DRM_IOCTL_ARMADA_GEM_PROP \
+	ARMADA_IOCTL(IOWR, GEM_PROP, gem_prop)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_put_image {
+	uint32_t flags;
+#define ARMADA_OVERLAY_TYPE_MASK          0x000000ff
+#define ARMADA_OVERLAY_YUV_PLANAR         0x00000001
+#define ARMADA_OVERLAY_YUV_PACKED         0x00000002
+#define ARMADA_OVERLAY_RGB                0x00000003
+#define ARMADA_OVERLAY_DEPTH_MASK		0x0000ff00
+#define ARMADA_OVERLAY_RGB24		0x00001000
+#define ARMADA_OVERLAY_RGB16		0x00002000
+#define ARMADA_OVERLAY_RGB15		0x00003000
+#define ARMADA_OVERLAY_YUV422		0x00000100
+#define ARMADA_OVERLAY_YUV411		0x00000200
+#define ARMADA_OVERLAY_YUV420		0x00000300
+#define ARMADA_OVERLAY_YUV410		0x00000400
+#define ARMADA_OVERLAY_SWAP_MASK		0x00ff0000
+#define ARMADA_OVERLAY_NO_SWAP		0x00000000
+#define ARMADA_OVERLAY_UV_SWAP		0x00010000
+#define ARMADA_OVERLAY_Y_SWAP		0x00020000
+#define ARMADA_OVERLAY_Y_AND_UV_SWAP	0x00030000
+#define ARMADA_OVERLAY_FLAGS_MASK		0xff000000
+#define ARMADA_OVERLAY_ENABLE		0x01000000
+	uint32_t bo_handle;
+	uint16_t stride_Y;
+	uint16_t stride_UV;
+	uint32_t offset_Y;
+	uint32_t offset_U;
+	uint32_t offset_V;
+	uint16_t src_width;
+	uint16_t src_height;
+	uint16_t src_scan_width;
+	uint16_t src_scan_height;
+	uint32_t crtc_id;
+	uint16_t dst_x;
+	uint16_t dst_y;
+	uint16_t dst_width;
+	uint16_t dst_height;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_PUT_IMAGE \
+	ARMADA_IOCTL(IOW, OVERLAY_PUT_IMAGE, overlay_put_image)
+
+/* Same as Intel I915 */
+struct drm_armada_overlay_attrs {
+	uint32_t flags;
+#define ARMADA_OVERLAY_UPDATE_ATTRS	(1<<0)
+#define ARMADA_OVERLAY_UPDATE_GAMMA	(1<<1)
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+	uint32_t gamma0;
+	uint32_t gamma1;
+	uint32_t gamma2;
+	uint32_t gamma3;
+	uint32_t gamma4;
+	uint32_t gamma5;
+};
+#define DRM_IOCTL_ARMADA_OVERLAY_ATTRS \
+	ARMADA_IOCTL(IOWR, OVERLAY_ATTRS, overlay_attrs)
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_ioctlP.h b/drivers/gpu/drm/armada/armada_ioctlP.h
new file mode 100644
index 0000000..8e6089e
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_ioctlP.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_IOCTLP_H
+#define ARMADA_IOCTLP_H
+
+#define ARMADA_IOCTL_PROTO(name)\
+extern int armada_##name##_ioctl(struct drm_device *, void *, struct drm_file *)
+
+ARMADA_IOCTL_PROTO(gem_create);
+ARMADA_IOCTL_PROTO(gem_create_phys);
+ARMADA_IOCTL_PROTO(gem_mmap);
+ARMADA_IOCTL_PROTO(gem_prop);
+ARMADA_IOCTL_PROTO(gem_pwrite);
+ARMADA_IOCTL_PROTO(overlay_put_image);
+ARMADA_IOCTL_PROTO(overlay_attrs);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_output.c b/drivers/gpu/drm/armada/armada_output.c
new file mode 100644
index 0000000..3b95f0c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "drm_helper.h"
+#include "armada_output.h"
+#include "armada_drm.h"
+
+struct armada_connector {
+	struct drm_connector conn;
+	const struct armada_output_type *type;
+};
+
+#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = conn->encoder;
+
+	return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
+}
+
+static enum drm_connector_status armada_drm_connector_detect(
+	struct drm_connector *conn, bool force)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+	enum drm_connector_status status = connector_status_disconnected;
+
+	if (dconn->type->detect) {
+		status = dconn->type->detect(conn, force);
+	} else {
+		struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+
+		if (enc)
+			status = encoder_helper_funcs(enc)->detect(enc, conn);
+	}
+
+	return status;
+}
+
+static void armada_drm_connector_destroy(struct drm_connector *conn)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	drm_sysfs_connector_remove(conn);
+	drm_connector_cleanup(conn);
+	kfree(dconn);
+}
+
+static int armada_drm_connector_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct armada_connector *dconn = drm_to_armada_conn(conn);
+
+	if (!dconn->type->set_property)
+		return -EINVAL;
+
+	return dconn->type->set_property(conn, property, value);
+}
+
+static const struct drm_connector_funcs armada_drm_conn_funcs = {
+	.dpms		= drm_helper_connector_dpms,
+	.fill_modes	= drm_helper_probe_single_connector_modes,
+	.detect		= armada_drm_connector_detect,
+	.destroy	= armada_drm_connector_destroy,
+	.set_property	= armada_drm_connector_set_property,
+};
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+void armada_drm_encoder_commit(struct drm_encoder *encoder)
+{
+	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
+{
+	return true;
+}
+
+/* Shouldn't this be a generic helper function? */
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int valid = MODE_BAD;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		valid = slave->slave_funcs->mode_valid(encoder, mode);
+	}
+	return valid;
+}
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
+	int rc = -EINVAL;
+
+	if (encoder) {
+		struct drm_encoder_slave *slave = to_encoder_slave(encoder);
+
+		rc = slave->slave_funcs->set_property(encoder, conn, property,
+						      value);
+	}
+	return rc;
+}
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data)
+{
+	struct armada_connector *dconn;
+	int ret;
+
+	dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
+	if (!dconn)
+		return -ENOMEM;
+
+	dconn->type = type;
+
+	ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
+				 type->connector_type);
+	if (ret) {
+		DRM_ERROR("unable to init connector\n");
+		goto err_destroy_dconn;
+	}
+
+	ret = type->create(&dconn->conn, data);
+	if (ret)
+		goto err_conn;
+
+	ret = drm_sysfs_connector_add(&dconn->conn);
+	if (ret)
+		goto err_sysfs;
+
+	return 0;
+
+ err_sysfs:
+	if (dconn->conn.encoder)
+		dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
+ err_conn:
+	drm_connector_cleanup(&dconn->conn);
+ err_destroy_dconn:
+	kfree(dconn);
+	return ret;
+}
diff --git a/drivers/gpu/drm/armada/armada_output.h b/drivers/gpu/drm/armada/armada_output.h
new file mode 100644
index 0000000..d655655
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_output.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_CONNETOR_H
+#define ARMADA_CONNETOR_H
+
+#define encoder_helper_funcs(encoder) \
+	((struct drm_encoder_helper_funcs *)encoder->helper_private)
+
+struct armada_output_type {
+	int connector_type;
+	enum drm_connector_status (*detect)(struct drm_connector *, bool);
+	int (*create)(struct drm_connector *, const void *);
+	int (*set_property)(struct drm_connector *, struct drm_property *, uint64_t);
+};
+
+struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn);
+
+void armada_drm_encoder_prepare(struct drm_encoder *encoder);
+void armada_drm_encoder_commit(struct drm_encoder *encoder);
+
+bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adj);
+
+int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode);
+
+int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value);
+
+int armada_output_create(struct drm_device *dev,
+	const struct armada_output_type *type, const void *data);
+
+#endif
diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c
new file mode 100644
index 0000000..68c4efd
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_overlay.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include "drm_helper.h"
+#include "armada_crtc.h"
+#include "armada_drm.h"
+#include "armada_gem.h"
+#include "armada_hw.h"
+#include "armada_ioctl.h"
+#include "armada_ioctlP.h"
+
+struct armada_overlay {
+	struct armada_gem_object *obj;
+	struct armada_gem_object *old_obj;
+	struct armada_crtc *dcrtc;
+	uint32_t crtc_id;
+	uint32_t cached_flags;
+	uint32_t cached_cfg;
+
+	uint32_t stride_yc;
+	uint32_t stride_uv;
+	uint32_t dst_yx;
+	uint32_t src_hw;
+	uint32_t dst_hw;
+	uint32_t cfg;
+
+	uint32_t color_key;
+	int32_t brightness;
+	uint32_t contrast;
+	uint32_t saturation;
+
+	struct armada_vbl_event vbl_update;
+
+	struct armada_regs update[13];
+
+	wait_queue_head_t vbl_wait;
+};
+
+static void armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
+{
+	uint32_t ov, v;
+
+	ov = v = readl_relaxed(ptr);
+	v = (v & ~mask) | val;
+	if (ov != v)
+		writel_relaxed(v, ptr);
+}
+
+static void armada_ovl_update(struct armada_crtc *dcrtc, void *data)
+{
+	struct armada_overlay *ovl = container_of(data, struct armada_overlay, update);
+	armada_drm_crtc_update_regs(dcrtc, data);
+	wake_up(&ovl->vbl_wait);
+}
+
+static void armada_ovl_update_attr(struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+	uint32_t key = ovl->color_key;
+        uint32_t r, g, b;
+
+        r = (key & 0x0000ff);
+        g = (key & 0x00ff00) >> 8;
+        b = (key & 0xff0000) >> 16;
+
+	writel_relaxed(r << 8 | r << 16 | r << 24, dcrtc->base + LCD_SPU_COLORKEY_Y);
+	writel_relaxed(g << 8 | g << 16 | g << 24, dcrtc->base + LCD_SPU_COLORKEY_U);
+	writel_relaxed(b << 8 | b << 16 | b << 24, dcrtc->base + LCD_SPU_COLORKEY_V);
+
+	writel_relaxed(0x00004000, dcrtc->base + LCD_SPU_CONTRAST);
+	writel_relaxed(0x40000000, dcrtc->base + LCD_SPU_SATURATION);
+	writel_relaxed(0x00002000, dcrtc->base + LCD_SPU_CBSH_HUE);
+
+	spin_lock_irq(&dcrtc->irq_lock);
+	armada_updatel(CFG_CKMODE_RGB | CFG_ALPHAM_GRA,
+		     CFG_CKMODE_MASK | CFG_ALPHAM_MASK | CFG_ALPHA_MASK,
+		     dcrtc->base + LCD_SPU_DMA_CTRL1);
+
+	armada_updatel(ADV_GRACOLORKEY, 0, dcrtc->base + LCD_SPU_ADV_REG);
+	spin_unlock_irq(&dcrtc->irq_lock);
+}
+
+static int armada_check_planar(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp;
+
+	DRM_DEBUG_DRIVER("stride Y%#x UV%#x offset Y%#x U%#x V%#x obj %#x\n",
+		args->stride_Y, args->stride_UV,
+		args->offset_Y, args->offset_U, args->offset_V,
+		obj->obj.size);
+
+	if (args->src_scan_width     > args->stride_Y ||
+	    args->src_scan_width / 2 > args->stride_UV)
+		return -EINVAL;
+
+	tmp = args->stride_Y * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	tmp = args->stride_UV * args->src_height;
+	if (tmp > obj->obj.size ||
+	    args->offset_U > obj->obj.size - tmp ||
+	    args->offset_V > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int armada_check_packed(struct drm_armada_overlay_put_image *args,
+	struct armada_gem_object *obj)
+{
+	uint32_t tmp = args->stride_Y * args->src_height;
+
+	if (args->src_scan_width * 2 > args->stride_Y ||
+	    tmp > obj->obj.size ||
+	    args->offset_Y > obj->obj.size - tmp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct armada_crtc *armada_crtc_lookup(struct drm_device *dev, uint32_t id)
+{
+	struct drm_crtc *crtc = drm_crtc_find(dev, id);
+	return crtc ? drm_to_armada_crtc(crtc) : NULL;
+}
+
+static void armada_ovl_release_old(struct armada_overlay *ovl)
+{
+	if (ovl->old_obj) {
+		drm_gem_object_unreference(&ovl->old_obj->obj);
+		ovl->old_obj = NULL;
+	}
+}
+
+/*
+ * This should be called with both dev->struct_mutex and
+ * the mode_config mutexes held.
+ */
+void armada_drm_overlay_off(struct drm_device *dev, struct armada_overlay *ovl)
+{
+	struct armada_crtc *dcrtc = ovl->dcrtc;
+
+	ovl->cfg = 0;
+
+	if (dcrtc) {
+		/* Disable overlay */
+		armada_drm_vbl_event_remove_unlocked(dcrtc, &ovl->vbl_update);
+
+		spin_lock_irq(&dcrtc->irq_lock);
+		armada_updatel(0, CFG_DMA_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0);
+		spin_unlock_irq(&dcrtc->irq_lock);
+
+		ovl->dcrtc->overlay = NULL;
+		ovl->dcrtc = NULL;
+		ovl->crtc_id = ~0;
+	}
+
+	armada_ovl_release_old(ovl);
+
+	if (ovl->obj) {
+		drm_gem_object_unreference(&ovl->obj->obj);
+		ovl->obj = NULL;
+	}
+}
+
+static int armada_ovl_check_dst(const struct drm_armada_overlay_put_image *args,
+	const struct drm_display_mode *mode)
+{
+	if (args->dst_x < mode->hdisplay &&
+	    args->dst_width <= mode->hdisplay - args->dst_x &&
+	    args->dst_y < mode->vdisplay &&
+	    args->dst_height <= mode->vdisplay - args->dst_y)
+		return 0;
+	return -EINVAL;
+}
+
+static int armada_ovl_compute_cfg(struct armada_overlay *ovl, uint32_t flags)
+{
+	uint32_t cfg = CFG_DMA_HSMOOTH | CFG_CBSH_ENA;
+
+	switch (flags & ARMADA_OVERLAY_TYPE_MASK) {
+	case ARMADA_OVERLAY_YUV_PLANAR:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:	/* Planar YUV422 */
+			cfg |= CFG_DMA_422;
+			break;
+		case ARMADA_OVERLAY_YUV420:	/* Planar YUV420 */
+			cfg |= CFG_DMA_420;
+			break;
+		default:
+			DRM_ERROR("bad planar depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("planar and requested swap\n");
+			return -EINVAL;
+		}
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_YUV_PACKED:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_YUV422:
+			cfg |= CFG_DMA_422PACK;
+			break;
+		default:
+			DRM_ERROR("bad packed depth\n");
+			return -EINVAL;
+		}
+		if (flags & (ARMADA_OVERLAY_SWAP_MASK & ~ARMADA_OVERLAY_Y_AND_UV_SWAP)) {
+			DRM_ERROR("bad overlay swap\n");
+			return -EINVAL;
+		}
+		/*
+		 *                            [0:7] [8:15] [16:23] [24:31]
+		 * 0:			YUY2:   Y     U       Y       V
+		 * UV_SWAP:		YVYU:   Y     V       Y       U
+		 * Y_SWAP:		UYVY:   U     Y       V       Y
+		 * Y_SWAP | UV_SWAP:	VYUY:   V     Y       U       Y
+		 *
+		 * Default ordering in memory:  U     Y0      V       Y1
+		 *
+		 * Image fourcc 59565955 UYVY flags 01020102 -> correct
+		 * Image fourcc 32595559 YUY2 flags 01000102 -> wrong U/V swapped
+		 */
+		if (flags & ARMADA_OVERLAY_UV_SWAP)
+			cfg |= CFG_DMA_SWAPUV;
+		if (!(flags & ARMADA_OVERLAY_Y_SWAP))
+			cfg ^= CFG_DMA_SWAPYU | CFG_DMA_SWAPUV;
+		cfg |= CFG_YUV2RGB_DMA;
+		break;
+
+	case ARMADA_OVERLAY_RGB:
+		switch (flags & ARMADA_OVERLAY_DEPTH_MASK) {
+		case ARMADA_OVERLAY_RGB24:	/* 3 byte RGB */
+			cfg |= CFG_DMA_888PACK;
+			break;
+		case ARMADA_OVERLAY_RGB16:        /* 2 byte RGB */
+			cfg |= CFG_DMA_565;
+			break;
+		case ARMADA_OVERLAY_RGB15:        /* 2 byte RGB */
+			cfg |= CFG_DMA_1555;
+			break;
+		default:
+			DRM_ERROR("bad RGB depth\n");
+			return -EINVAL;
+		}
+		/* Planar formats have no swaps */
+		if (flags & ARMADA_OVERLAY_SWAP_MASK) {
+			DRM_ERROR("RGB and requested swap\n");
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		DRM_ERROR("bad overlay type\n");
+		return -EINVAL;
+	}
+
+	cfg |= CFG_DMA_ENA;
+
+	ovl->cached_flags = flags;
+	ovl->cached_cfg = cfg;
+	return 0;
+}
+
+int armada_overlay_put_image_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_put_image *args = data;
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+	struct armada_gem_object *obj;
+	struct armada_crtc *dcrtc;
+	uint32_t stride_uv, stride_yc, src_hw, dst_hw, dst_yx;
+	bool planar = false;
+	int ret, idx;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (!(args->flags & ARMADA_OVERLAY_ENABLE)) {
+		mutex_lock(&dev->mode_config.mutex);
+		mutex_lock(&dev->struct_mutex);
+
+		armada_drm_overlay_off(dev, ovl);
+
+		mutex_unlock(&dev->struct_mutex);
+		mutex_unlock(&dev->mode_config.mutex);
+
+		return 0;
+	}
+
+//	DRM_DEBUG_DRIVER("flags %x handle %x src %dx%d dst %dx%d+%d+%d\n",
+//		args->flags, args->bo_handle, args->src_scan_width, args->src_scan_height,
+//		args->dst_width, args->dst_height, args->dst_x, args->dst_y);
+
+	if (!ovl->dcrtc || ovl->crtc_id != args->crtc_id) {
+		dcrtc = armada_crtc_lookup(dev, args->crtc_id);
+		if (!dcrtc)
+			return -ENOENT;
+	} else {
+		dcrtc = ovl->dcrtc;
+	}
+
+	if (args->flags != ovl->cached_flags) {
+		ret = armada_ovl_compute_cfg(ovl, args->flags);
+		if (ret)
+			return ret;
+	}
+
+	obj = armada_gem_object_lookup(dev, file, args->bo_handle);
+	if (!obj)
+		return -ENOENT;
+
+	ret = wait_event_timeout(ovl->vbl_wait, list_empty(&ovl->vbl_update.node), HZ/25);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&dev->mode_config.mutex);
+	mutex_lock(&dev->struct_mutex);
+
+	/* Prevent updates */
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	if (ovl->dcrtc != dcrtc) {
+		armada_drm_overlay_off(dev, ovl);
+
+		ovl->crtc_id = args->crtc_id;
+		ovl->dcrtc = dcrtc;
+		dcrtc->overlay = ovl;
+
+		armada_ovl_update_attr(ovl);
+	}
+
+	ret = armada_ovl_check_dst(args, &ovl->dcrtc->crtc.mode);
+	if (ret)
+		goto err_unref;
+
+	planar = (args->flags & ARMADA_OVERLAY_TYPE_MASK) == ARMADA_OVERLAY_YUV_PLANAR;
+	if (planar)
+		ret = armada_check_planar(args, obj);
+	else
+		ret = armada_check_packed(args, obj);
+	if (ret)
+		goto err_unref;
+
+	if (ovl->dcrtc->interlaced) {
+		args->dst_y /= 2;
+		args->dst_height /= 2;
+	}
+
+	idx = 0;
+	if (ovl->obj != obj) {
+		uint32_t start_y, start_u, start_v;
+
+		/* switch to new object */
+		armada_ovl_release_old(ovl);
+		ovl->old_obj = ovl->obj;
+		ovl->obj = obj;
+
+		start_y = obj->dev_addr + args->offset_Y;
+		if (planar) {
+			start_u = obj->dev_addr + args->offset_U;
+			start_v = obj->dev_addr + args->offset_V;
+		} else {
+			start_u = start_y;
+			start_v = start_y;
+		}
+
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y0);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U0);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V0);
+		armada_reg_queue_set(ovl->update, idx, start_y, LCD_SPU_DMA_START_ADDR_Y1);
+		armada_reg_queue_set(ovl->update, idx, start_u, LCD_SPU_DMA_START_ADDR_U1);
+		armada_reg_queue_set(ovl->update, idx, start_v, LCD_SPU_DMA_START_ADDR_V1);
+	} else {
+		drm_gem_object_unreference(&obj->obj);
+	}
+
+	stride_yc = args->stride_Y << 16 | args->stride_Y;
+	stride_uv = args->stride_UV << 16 | args->stride_UV;
+
+	if (ovl->stride_yc != stride_yc || ovl->stride_uv != stride_uv) {
+		ovl->stride_yc = stride_yc;
+		ovl->stride_uv = stride_uv;
+		armada_reg_queue_set(ovl->update, idx, stride_yc, LCD_SPU_DMA_PITCH_YC);
+		armada_reg_queue_set(ovl->update, idx, stride_uv, LCD_SPU_DMA_PITCH_UV);
+	}
+
+	src_hw = args->src_scan_height << 16 | args->src_scan_width;
+	dst_hw = args->dst_height << 16 | args->dst_width;
+	if (ovl->src_hw != src_hw || ovl->dst_hw != dst_hw) {
+		ovl->src_hw = src_hw;
+		ovl->dst_hw = dst_hw;
+
+		armada_reg_queue_set(ovl->update, idx, dst_hw, LCD_SPU_DZM_HPXL_VLN);
+		armada_reg_queue_set(ovl->update, idx, src_hw, LCD_SPU_DMA_HPXL_VLN);
+	}
+
+	dst_yx = args->dst_y << 16 | args->dst_x;
+	if (ovl->dst_yx != dst_yx) {
+		ovl->dst_yx = dst_yx;
+		armada_reg_queue_set(ovl->update, idx, dst_yx, LCD_SPU_DMA_OVSA_HPXL_VLN);
+	}
+
+	/* Update overlay DMA settings */
+	if (ovl->cfg != ovl->cached_cfg) {
+		ovl->cfg = ovl->cached_cfg;
+		armada_reg_queue_mod(ovl->update, idx, ovl->cached_cfg,
+			CFG_DMAFORMAT | CFG_DMA_FTOGGLE | CFG_DMA_HSMOOTH |
+			CFG_DMA_TSTMODE | CFG_DMA_SWAPRB | CFG_DMA_SWAPUV |
+			CFG_DMA_SWAPYU | CFG_YUV2RGB_DMA | CFG_DMA_ENA,
+			LCD_SPU_DMA_CTRL0);
+	}
+	if (idx) {
+		armada_reg_queue_end(ovl->update, idx);
+		armada_drm_vbl_event_add(dcrtc, &ovl->vbl_update);
+	}
+
+ err_unref:
+	if (ret)
+		drm_gem_object_unreference(&obj->obj);
+	mutex_unlock(&dev->struct_mutex);
+	mutex_unlock(&dev->mode_config.mutex);
+
+	return ret;
+}
+
+int armada_overlay_attrs_ioctl(struct drm_device *dev, void *data,
+	struct drm_file *file)
+{
+	struct drm_armada_overlay_attrs *args = data;
+        struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (!ovl) {
+		DRM_DEBUG_DRIVER("no overlay");
+		return -ENODEV;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_ATTRS) {
+		if (args->brightness < -128 || args->brightness > 127 ||
+		    args->contrast > 255 || args->saturation > 1023)
+			return -EINVAL;
+
+		ovl->color_key = args->color_key;
+		ovl->brightness = args->brightness;
+		ovl->contrast = args->contrast;
+		ovl->saturation = args->saturation;
+
+		mutex_lock(&dev->mode_config.mutex);
+		if (ovl->dcrtc)
+			armada_ovl_update_attr(ovl);
+		mutex_unlock(&dev->mode_config.mutex);
+	} else {
+		args->color_key = ovl->color_key;
+		args->brightness = ovl->brightness;
+		args->contrast = ovl->contrast;
+		args->saturation = ovl->saturation;
+	}
+
+	if (args->flags & ARMADA_OVERLAY_UPDATE_GAMMA) {
+		/* args->gamma0..5 */
+	}
+
+	return 0;
+}
+
+int armada_overlay_create(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl;
+
+	ovl = kzalloc(sizeof(*ovl), GFP_KERNEL);
+	if (!ovl)
+		return -ENOMEM;
+
+	priv->overlay = ovl;
+
+	init_waitqueue_head(&ovl->vbl_wait);
+	armada_drm_vbl_event_init(&ovl->vbl_update, armada_ovl_update, ovl->update);
+
+	ovl->color_key = 0x0101fe;
+	ovl->brightness = -19;
+	ovl->contrast = 75;
+	ovl->saturation = 146;
+
+	return 0;
+}
+
+void armada_overlay_destroy(struct drm_device *dev)
+{
+	struct armada_private *priv = dev->dev_private;
+	struct armada_overlay *ovl = priv->overlay;
+
+	if (ovl->dcrtc)
+		armada_drm_vbl_event_remove_unlocked(ovl->dcrtc, &ovl->vbl_update);
+
+	kfree(ovl);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.c b/drivers/gpu/drm/armada/armada_slave.c
new file mode 100644
index 0000000..422a345
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include "armada_drm.h"
+#include "armada_output.h"
+#include "armada_slave.h"
+
+static int armada_drm_slave_get_modes(struct drm_connector *conn)
+{
+	struct drm_encoder *enc = armada_drm_connector_encoder(conn);
+	int count = 0;
+
+	if (enc) {
+		struct drm_encoder_slave *slave = to_encoder_slave(enc);
+
+		count = slave->slave_funcs->get_modes(enc, conn);
+	}
+
+	return count;
+}
+
+static void armada_drm_slave_destroy(struct drm_encoder *enc)
+{
+	struct drm_encoder_slave *slave = to_encoder_slave(enc);
+	struct i2c_client *client = drm_i2c_encoder_get_client(enc);
+
+	if (slave->slave_funcs)
+		slave->slave_funcs->destroy(enc);
+	if (client)
+		i2c_put_adapter(client->adapter);
+
+	drm_encoder_cleanup(&slave->base);
+	kfree(slave);
+}
+
+static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
+	.destroy	= armada_drm_slave_destroy,
+};
+
+static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
+	.get_modes	= armada_drm_slave_get_modes,
+	.mode_valid	= armada_drm_slave_encoder_mode_valid,
+	.best_encoder	= armada_drm_connector_encoder,
+};
+
+static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
+	.dpms = drm_i2c_encoder_dpms,
+	.save = drm_i2c_encoder_save,
+	.restore = drm_i2c_encoder_restore,
+	.mode_fixup = drm_i2c_encoder_mode_fixup,
+	.prepare = drm_i2c_encoder_prepare,
+	.commit = drm_i2c_encoder_commit,
+	.mode_set = drm_i2c_encoder_mode_set,
+	.detect = drm_i2c_encoder_detect,
+};
+
+static int armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
+{
+	const struct armada_drm_slave_config *config = data;
+	struct drm_encoder_slave *slave;
+	struct i2c_adapter *adap;
+	int ret;
+
+	conn->interlace_allowed = config->interlace_allowed;
+	conn->doublescan_allowed = config->doublescan_allowed;
+	conn->polled = config->polled;
+
+	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
+
+	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+	if (!slave)
+		return -ENOMEM;
+
+	slave->base.possible_crtcs = config->crtcs;
+
+	adap = i2c_get_adapter(config->i2c_adapter_id);
+	if (!adap) {
+		kfree(slave);
+		return -EPROBE_DEFER;
+	}
+
+	ret = drm_encoder_init(conn->dev, &slave->base,
+			       &armada_drm_slave_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret) {
+		DRM_ERROR("unable to init encoder\n");
+		i2c_put_adapter(adap);
+		kfree(slave);
+		return ret;
+	}
+
+	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
+	i2c_put_adapter(adap);
+	if (ret) {
+		DRM_ERROR("unable to init encoder slave\n");
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
+
+	ret = slave->slave_funcs->create_resources(&slave->base, conn);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
+	if (ret) {
+		armada_drm_slave_destroy(&slave->base);
+		return ret;
+	}
+
+	conn->encoder = &slave->base;
+
+	return ret;
+}
+
+static const struct armada_output_type armada_drm_conn_slave = {
+	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
+	.create		= armada_drm_conn_slave_create,
+	.set_property	= armada_drm_slave_encoder_set_property,
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *config)
+{
+	return armada_output_create(dev, &armada_drm_conn_slave, config);
+}
diff --git a/drivers/gpu/drm/armada/armada_slave.h b/drivers/gpu/drm/armada/armada_slave.h
new file mode 100644
index 0000000..bf2374c
--- /dev/null
+++ b/drivers/gpu/drm/armada/armada_slave.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef ARMADA_SLAVE_H
+#define ARMADA_SLAVE_H
+
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+
+struct armada_drm_slave_config {
+	int i2c_adapter_id;
+	uint32_t crtcs;
+	uint8_t polled;
+	bool interlace_allowed;
+	bool doublescan_allowed;
+	struct i2c_board_info info;
+};
+
+int armada_drm_connector_slave_create(struct drm_device *dev,
+	const struct armada_drm_slave_config *);
+
+#endif
diff --git a/drivers/gpu/drm/armada/drm_helper.h b/drivers/gpu/drm/armada/drm_helper.h
new file mode 100644
index 0000000..d9f2e8d
--- /dev/null
+++ b/drivers/gpu/drm/armada/drm_helper.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef DRM_HELPER_H
+#define DRM_HELPER_H
+
+/* Useful helpers I wish the DRM core would provide */
+
+#include <drm/drmP.h>
+
+static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
+	return mo ? obj_to_crtc(mo) : NULL;
+}
+
+static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
+	uint32_t id)
+{
+	struct drm_mode_object *mo;
+	mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
+	return mo ? obj_to_encoder(mo) : NULL;
+}
+
+#endif
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 4/4] DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB
  2013-06-09 19:06 ` Russell King - ARM Linux
@ 2013-06-10 22:51   ` Russell King
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:51 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/armada_crtc.c |  125 +++++++++++-----------------------
 drivers/gpu/drm/armada/armada_hw.h   |    6 +-
 2 files changed, 43 insertions(+), 88 deletions(-)

diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index b833014..3e2f9d3 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -493,7 +493,8 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
 	dcrtc->v[1].spu_adv_reg = val << 20 | val;
 
 	/* Always enable RGB hardware cursor mode */
-	dcrtc->v[1].spu_adv_reg |= ADV_HWC32ENABLE;
+	dcrtc->v[1].spu_adv_reg |= ADV_HWC32ENABLE |
+				   ADV_HWC32ARGB | ADV_HWC32BLEND;
 
 	if (interlaced) {
 		/* Odd interlaced frame */
@@ -641,100 +642,46 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
 };
 
 #ifdef CONFIG_DRM_ARMADA_CURSOR
-static void armada_load_cursor_rgb(void __iomem *base, uint32_t *pix,
+static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix,
 	unsigned stride, unsigned width, unsigned height)
 {
-	uint32_t r, g, b, addr;
-	unsigned y, n;
+	uint32_t addr;
+	unsigned y;
 
-	for (r = g = b = addr = n = y = 0; y < height; y++) {
+	addr = SRAM_HWC32_RAM1;
+	for (y = 0; y < height; y++) {
 		uint32_t *p = &pix[y * stride];
 		unsigned x;
 
 		for (x = 0; x < width; x++, p++) {
-			r >>= 8;
-			r |= 0xff000000 & (*p << 8);
-			g >>= 8;
-			g |= 0xff000000 & (*p << 16);
-			b >>= 8;
-			b |= 0xff000000 & (*p << 24);
-			if (x == 0 || y == 0) {
-				b |= 0xff000000;
-				g |= 0xff000000;
-				r &= ~0xff000000;
-			}
-			if (++n == 4) {
-				writel_relaxed(r,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_RAMR,
-					       base + LCD_SPU_SRAM_CTRL);
-				writel_relaxed(g,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_RAMG,
-					       base + LCD_SPU_SRAM_CTRL);
-				writel_relaxed(b,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_RAMB,
-					       base + LCD_SPU_SRAM_CTRL);
-				addr += 1;
-				if ((addr & 255) == 0)
-					addr += 0xf00;
-				n = 0;
-			}
+			uint32_t val = *p;
+
+			val = (val & 0xff00ff00) |
+			      (val & 0x000000ff) << 16 |
+			      (val & 0x00ff0000) >> 16;
+
+			writel_relaxed(val,
+				       base + LCD_SPU_SRAM_WRDAT);
+			writel_relaxed(addr | SRAM_WRITE,
+				       base + LCD_SPU_SRAM_CTRL);
+			addr += 1;
+			if ((addr & 0x00ff) == 0)
+				addr += 0xf00;
+			if ((addr & 0x30ff) == 0)
+				addr = SRAM_HWC32_RAM2;
 		}
 	}
-	if (n) {
-		r >>= 8 * (4 - n);
-		g >>= 8 * (4 - n);
-		b >>= 8 * (4 - n);
-		writel_relaxed(r, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMR,
-			       base + LCD_SPU_SRAM_CTRL);
-		writel_relaxed(g, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMG,
-			       base + LCD_SPU_SRAM_CTRL);
-		writel_relaxed(b, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMB,
-			       base + LCD_SPU_SRAM_CTRL);
-	}
 }
 
-static void armada_load_cursor_alpha(void __iomem *base, uint32_t *pix,
-	unsigned stride, unsigned width, unsigned height)
+static void armada_drm_crtc_cursor_tran(void __iomem *base)
 {
-	uint32_t data, addr, ram_cmd = SRAM_WRITE | SRAM_HWC32_TRAN;
-	unsigned y, n;
+	unsigned addr;
 
-	for (data = addr = n = y = 0; y < height; y++) {
-		uint32_t *p = &pix[y * stride];
-		unsigned x;
-
-		for (x = 0; x < width; x++, p++) {
-			data >>= 2;
-			if (*p >= 0x80000000)
-				data |= 0x40000000;
-			if (x == 0 || y == 0)
-				data |= 0x40000000;
-			if (++n == 16) {
-				writel_relaxed(data,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_TRAN,
-					       base + LCD_SPU_SRAM_CTRL);
-				addr += 1;
-				if ((addr & 255) == 0)
-					addr += 0xf00;
-				n = 0;
-			}
-		}
-	}
-	if (n) {
-		data >>= 2 * (16 - n);
-		writel_relaxed(data, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | ram_cmd, base + LCD_SPU_SRAM_CTRL);
+	for (addr = 0; addr < 256; addr++) {
+		/* write the default value */
+		writel_relaxed(0x55555555, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_TRAN,
+			       base + LCD_SPU_SRAM_CTRL);
 	}
 }
 
@@ -742,6 +689,7 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 {
 	uint32_t xoff, xscr, w = dcrtc->cursor_w, s;
 	uint32_t yoff, yscr, h = dcrtc->cursor_h;
+	uint32_t para1;
 
 	/*
 	 * Calculate the visible width and height of the cursor,
@@ -790,9 +738,14 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 		return 0;
 	}
 
+	para1 = readl_relaxed(dcrtc->base + LCD_SPU_SRAM_PARA1);
 	armada_updatel(CFG_CSB_256x32, CFG_PDWN256x32,
 		     dcrtc->base + LCD_SPU_SRAM_PARA1);
 
+	/* Initialize the transparency if the SRAM was powered down */
+	if (!(para1 & CFG_CSB_256x32))
+		armada_drm_crtc_cursor_tran(dcrtc->base);
+
 	if (dcrtc->cursor_hw_sz != (h << 16 | w) || reload) {
 		struct armada_gem_object *obj = dcrtc->cursor_obj;
 		uint32_t *pix;
@@ -806,8 +759,7 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 		/* Set the top-left corner of the cursor image */
 		pix = obj->addr;
 		pix += yoff * s + xoff;
-		armada_load_cursor_rgb(dcrtc->base, pix, s, w, h);
-		armada_load_cursor_alpha(dcrtc->base, pix, s, w, h);
+		armada_load_cursor_argb(dcrtc->base, pix, s, w, h);
 	}
 
 	/* Reload the cursor position, size and enable in the IRQ handler */
@@ -835,8 +787,8 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
 	int ret;
 
 	if (handle && w > 0 && h > 0) {
-		/* maximum size is 64x64 */
-		if (w > 64 || h > 64)
+		/* maximum size is 64x32 or 32x64 */
+		if (w > 64 || h > 64 || (w > 32 && h > 32))
 			return -ENOMEM;
 
 		obj = armada_gem_object_lookup(dev, file, handle);
@@ -1023,6 +975,7 @@ int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
 	writel_relaxed(0x0000e000, dcrtc->base + LCD_SPU_SRAM_PARA1);
 	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
 	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
+	writel_relaxed(CFG_PDWN256x32, dcrtc->base + LCD_SPU_SRAM_PARA1);
 
 	/* Lower the watermark so to eliminate jitter at higher bandwidths */
 	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h
index 58d2a20..0c2937b 100644
--- a/drivers/gpu/drm/armada/armada_hw.h
+++ b/drivers/gpu/drm/armada/armada_hw.h
@@ -171,8 +171,10 @@ enum {
 	SRAM_READ	= 0 << 14,
 	SRAM_WRITE	= 2 << 14,
 	SRAM_INIT	= 3 << 14,
-	SRAM_HWC32_RAMR	= 0xc << 8,
-	SRAM_HWC32_RAMG	= 0xd << 8,
+	SRAM_HWC32_RAM1	= 0xc << 8,
+	SRAM_HWC32_RAM2	= 0xd << 8,
+	SRAM_HWC32_RAMR	= SRAM_HWC32_RAM1,
+	SRAM_HWC32_RAMG	= SRAM_HWC32_RAM2,
 	SRAM_HWC32_RAMB	= 0xe << 8,
 	SRAM_HWC32_TRAN	= 0xf << 8,
 	SRAM_HWC	= 0xf << 8,
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC v3 4/4] DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB
@ 2013-06-10 22:51   ` Russell King
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King @ 2013-06-10 22:51 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Jason Cooper, David Airlie, dri-devel, Rob Clark,
	Darren Etheridge, Sebastian Hesselbarth

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/armada/armada_crtc.c |  125 +++++++++++-----------------------
 drivers/gpu/drm/armada/armada_hw.h   |    6 +-
 2 files changed, 43 insertions(+), 88 deletions(-)

diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index b833014..3e2f9d3 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -493,7 +493,8 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
 	dcrtc->v[1].spu_adv_reg = val << 20 | val;
 
 	/* Always enable RGB hardware cursor mode */
-	dcrtc->v[1].spu_adv_reg |= ADV_HWC32ENABLE;
+	dcrtc->v[1].spu_adv_reg |= ADV_HWC32ENABLE |
+				   ADV_HWC32ARGB | ADV_HWC32BLEND;
 
 	if (interlaced) {
 		/* Odd interlaced frame */
@@ -641,100 +642,46 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
 };
 
 #ifdef CONFIG_DRM_ARMADA_CURSOR
-static void armada_load_cursor_rgb(void __iomem *base, uint32_t *pix,
+static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix,
 	unsigned stride, unsigned width, unsigned height)
 {
-	uint32_t r, g, b, addr;
-	unsigned y, n;
+	uint32_t addr;
+	unsigned y;
 
-	for (r = g = b = addr = n = y = 0; y < height; y++) {
+	addr = SRAM_HWC32_RAM1;
+	for (y = 0; y < height; y++) {
 		uint32_t *p = &pix[y * stride];
 		unsigned x;
 
 		for (x = 0; x < width; x++, p++) {
-			r >>= 8;
-			r |= 0xff000000 & (*p << 8);
-			g >>= 8;
-			g |= 0xff000000 & (*p << 16);
-			b >>= 8;
-			b |= 0xff000000 & (*p << 24);
-			if (x == 0 || y == 0) {
-				b |= 0xff000000;
-				g |= 0xff000000;
-				r &= ~0xff000000;
-			}
-			if (++n == 4) {
-				writel_relaxed(r,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_RAMR,
-					       base + LCD_SPU_SRAM_CTRL);
-				writel_relaxed(g,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_RAMG,
-					       base + LCD_SPU_SRAM_CTRL);
-				writel_relaxed(b,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_RAMB,
-					       base + LCD_SPU_SRAM_CTRL);
-				addr += 1;
-				if ((addr & 255) == 0)
-					addr += 0xf00;
-				n = 0;
-			}
+			uint32_t val = *p;
+
+			val = (val & 0xff00ff00) |
+			      (val & 0x000000ff) << 16 |
+			      (val & 0x00ff0000) >> 16;
+
+			writel_relaxed(val,
+				       base + LCD_SPU_SRAM_WRDAT);
+			writel_relaxed(addr | SRAM_WRITE,
+				       base + LCD_SPU_SRAM_CTRL);
+			addr += 1;
+			if ((addr & 0x00ff) == 0)
+				addr += 0xf00;
+			if ((addr & 0x30ff) == 0)
+				addr = SRAM_HWC32_RAM2;
 		}
 	}
-	if (n) {
-		r >>= 8 * (4 - n);
-		g >>= 8 * (4 - n);
-		b >>= 8 * (4 - n);
-		writel_relaxed(r, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMR,
-			       base + LCD_SPU_SRAM_CTRL);
-		writel_relaxed(g, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMG,
-			       base + LCD_SPU_SRAM_CTRL);
-		writel_relaxed(b, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_RAMB,
-			       base + LCD_SPU_SRAM_CTRL);
-	}
 }
 
-static void armada_load_cursor_alpha(void __iomem *base, uint32_t *pix,
-	unsigned stride, unsigned width, unsigned height)
+static void armada_drm_crtc_cursor_tran(void __iomem *base)
 {
-	uint32_t data, addr, ram_cmd = SRAM_WRITE | SRAM_HWC32_TRAN;
-	unsigned y, n;
+	unsigned addr;
 
-	for (data = addr = n = y = 0; y < height; y++) {
-		uint32_t *p = &pix[y * stride];
-		unsigned x;
-
-		for (x = 0; x < width; x++, p++) {
-			data >>= 2;
-			if (*p >= 0x80000000)
-				data |= 0x40000000;
-			if (x == 0 || y == 0)
-				data |= 0x40000000;
-			if (++n == 16) {
-				writel_relaxed(data,
-					       base + LCD_SPU_SRAM_WRDAT);
-				writel_relaxed(addr |
-					       SRAM_WRITE | SRAM_HWC32_TRAN,
-					       base + LCD_SPU_SRAM_CTRL);
-				addr += 1;
-				if ((addr & 255) == 0)
-					addr += 0xf00;
-				n = 0;
-			}
-		}
-	}
-	if (n) {
-		data >>= 2 * (16 - n);
-		writel_relaxed(data, base + LCD_SPU_SRAM_WRDAT);
-		writel_relaxed(addr | ram_cmd, base + LCD_SPU_SRAM_CTRL);
+	for (addr = 0; addr < 256; addr++) {
+		/* write the default value */
+		writel_relaxed(0x55555555, base + LCD_SPU_SRAM_WRDAT);
+		writel_relaxed(addr | SRAM_WRITE | SRAM_HWC32_TRAN,
+			       base + LCD_SPU_SRAM_CTRL);
 	}
 }
 
@@ -742,6 +689,7 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 {
 	uint32_t xoff, xscr, w = dcrtc->cursor_w, s;
 	uint32_t yoff, yscr, h = dcrtc->cursor_h;
+	uint32_t para1;
 
 	/*
 	 * Calculate the visible width and height of the cursor,
@@ -790,9 +738,14 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 		return 0;
 	}
 
+	para1 = readl_relaxed(dcrtc->base + LCD_SPU_SRAM_PARA1);
 	armada_updatel(CFG_CSB_256x32, CFG_PDWN256x32,
 		     dcrtc->base + LCD_SPU_SRAM_PARA1);
 
+	/* Initialize the transparency if the SRAM was powered down */
+	if (!(para1 & CFG_CSB_256x32))
+		armada_drm_crtc_cursor_tran(dcrtc->base);
+
 	if (dcrtc->cursor_hw_sz != (h << 16 | w) || reload) {
 		struct armada_gem_object *obj = dcrtc->cursor_obj;
 		uint32_t *pix;
@@ -806,8 +759,7 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload)
 		/* Set the top-left corner of the cursor image */
 		pix = obj->addr;
 		pix += yoff * s + xoff;
-		armada_load_cursor_rgb(dcrtc->base, pix, s, w, h);
-		armada_load_cursor_alpha(dcrtc->base, pix, s, w, h);
+		armada_load_cursor_argb(dcrtc->base, pix, s, w, h);
 	}
 
 	/* Reload the cursor position, size and enable in the IRQ handler */
@@ -835,8 +787,8 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
 	int ret;
 
 	if (handle && w > 0 && h > 0) {
-		/* maximum size is 64x64 */
-		if (w > 64 || h > 64)
+		/* maximum size is 64x32 or 32x64 */
+		if (w > 64 || h > 64 || (w > 32 && h > 32))
 			return -ENOMEM;
 
 		obj = armada_gem_object_lookup(dev, file, handle);
@@ -1023,6 +975,7 @@ int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
 	writel_relaxed(0x0000e000, dcrtc->base + LCD_SPU_SRAM_PARA1);
 	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
 	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
+	writel_relaxed(CFG_PDWN256x32, dcrtc->base + LCD_SPU_SRAM_PARA1);
 
 	/* Lower the watermark so to eliminate jitter at higher bandwidths */
 	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h
index 58d2a20..0c2937b 100644
--- a/drivers/gpu/drm/armada/armada_hw.h
+++ b/drivers/gpu/drm/armada/armada_hw.h
@@ -171,8 +171,10 @@ enum {
 	SRAM_READ	= 0 << 14,
 	SRAM_WRITE	= 2 << 14,
 	SRAM_INIT	= 3 << 14,
-	SRAM_HWC32_RAMR	= 0xc << 8,
-	SRAM_HWC32_RAMG	= 0xd << 8,
+	SRAM_HWC32_RAM1	= 0xc << 8,
+	SRAM_HWC32_RAM2	= 0xd << 8,
+	SRAM_HWC32_RAMR	= SRAM_HWC32_RAM1,
+	SRAM_HWC32_RAMG	= SRAM_HWC32_RAM2,
 	SRAM_HWC32_RAMB	= 0xe << 8,
 	SRAM_HWC32_TRAN	= 0xf << 8,
 	SRAM_HWC	= 0xf << 8,
-- 
1.7.4.4

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 22:49                 ` Rob Clark
@ 2013-06-10 22:56                   ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 22:56 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
> I guess in the short term, the best I can think is keep those phys
> ioctls as a small patch on top of the upstream driver.  It is ok to
> leave place-holder ioctl #'s in the upstream driver so that the ioctl
> #'s don't shift when you rebase.  And then try to get the vendor to
> add support for dmabuf so that the patch on top of upstream can
> eventually be dropped.  Maybe someone else has a better suggestion,
> but I don't think we can merge those phys ioctls upstream, and I'd
> really hate to 'throw the baby out with the bathwater' in this case
> and not at least get the modesetting part of the driver merged.

What you're saying is basically:

1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
   on existing gstreamer, xbmc and other implementations without someone
   rewriting all that code.

2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
   the GEM objects to the GPU libraries in userspace, thereby preventing
   any kind of GPU based acceleration.

That makes the driver just be a dumb scanout only driver.  Sorry,
that *really* does not interest me one bit, because the CPU doing
framebuffer accesses is pig slow.

There's really no point in such a driver; people might as well just
use the standard fbdev driver instead.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 22:56                   ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 22:56 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jason Cooper, David Airlie, dri-devel, Darren Etheridge,
	linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
> I guess in the short term, the best I can think is keep those phys
> ioctls as a small patch on top of the upstream driver.  It is ok to
> leave place-holder ioctl #'s in the upstream driver so that the ioctl
> #'s don't shift when you rebase.  And then try to get the vendor to
> add support for dmabuf so that the patch on top of upstream can
> eventually be dropped.  Maybe someone else has a better suggestion,
> but I don't think we can merge those phys ioctls upstream, and I'd
> really hate to 'throw the baby out with the bathwater' in this case
> and not at least get the modesetting part of the driver merged.

What you're saying is basically:

1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
   on existing gstreamer, xbmc and other implementations without someone
   rewriting all that code.

2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
   the GEM objects to the GPU libraries in userspace, thereby preventing
   any kind of GPU based acceleration.

That makes the driver just be a dumb scanout only driver.  Sorry,
that *really* does not interest me one bit, because the CPU doing
framebuffer accesses is pig slow.

There's really no point in such a driver; people might as well just
use the standard fbdev driver instead.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 22:32             ` Russell King - ARM Linux
@ 2013-06-10 23:12               ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:12 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 6:32 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
>> On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
>> > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
>> > <linux@arm.linux.org.uk> wrote:
>> >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
>> >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
>> >>> <rmk+kernel@arm.linux.org.uk> wrote:
>> >>> > This patch adds support for the pair of LCD controllers on the Marvell
>> >>> > Armada 510 SoCs.  This driver supports:
>> >>> > - multiple contiguous scanout buffers for video and graphics
>> >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>> >>> >   acceleration
>> >>> > - dual lcd0 and lcd1 crt operation
>> >>> > - video overlay on each LCD crt
>> >>>
>> >>> Any particular reason for not exposing the overlays as drm_plane's?
>> >>> That is probably something that should change before merging the
>> >>> driver.
>> >>
>> >> Only through not understanding much of DRM when I started this.
>> >> Provided DRM planes can do everything that I'm already doing with
>> >> the overlay support, then yes.  Otherwise, I want to stick with this
>> >> which is modelled after the i915 overlay interface.
>>
>> Oh i915 overlays aren't a good reason ;-) I've done those way back
>> when drm didn't have any plane infrastructure and pretty much as my
>> very contribution. So totally lacked any clue.
>>
>> If I can scrap together a bit of time I want to port the legacy
>> overlay code over to drm planes (and remap the current ioctl interface
>> to the drm plane interface for backwards compat). But atm that's
>> crushed under a giant pile of other todo items.
>
> Aren't we all :(  The amount of time I have to touch this has reduced
> substantially over the last couple of months, which is why there's been
> a few weeks between the RFC's for this.
>
> The issue here is that with the overlay interface, all I have to do
> with one of these pass-by-phys-address things which the X server gets
> is to:
>
> 1. associate the phys address with a gem object
> 2. pass it in via the overlay interface
>
> With the DRM plane stuff, this gets more complicated:
>
> 1. associate the phys address with a gem object
> 2. call another driver private ioctl to bind the gem object to a framebuffer
>    (there is no support for this in the generic ioctl API afaics) which
>    has to allocate and setup a framebuffer
> 3. use setplane to update the plane
>
> That all increases the expense of overlay, and adds further memory
> allocations to the path (and frees when the previous framebuffer is
> discarded.)
>
> That all makes for higher load due to more CPU expense.

Nothing prevents you from having a driver private ioctl on top of drm
plane to combine this all into one ioctl in addition to supporting the
standard plane interface

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:12               ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:12 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: linux-arm-kernel, dri-devel, Jason Cooper, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 6:32 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
>> On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
>> > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
>> > <linux@arm.linux.org.uk> wrote:
>> >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
>> >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
>> >>> <rmk+kernel@arm.linux.org.uk> wrote:
>> >>> > This patch adds support for the pair of LCD controllers on the Marvell
>> >>> > Armada 510 SoCs.  This driver supports:
>> >>> > - multiple contiguous scanout buffers for video and graphics
>> >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
>> >>> >   acceleration
>> >>> > - dual lcd0 and lcd1 crt operation
>> >>> > - video overlay on each LCD crt
>> >>>
>> >>> Any particular reason for not exposing the overlays as drm_plane's?
>> >>> That is probably something that should change before merging the
>> >>> driver.
>> >>
>> >> Only through not understanding much of DRM when I started this.
>> >> Provided DRM planes can do everything that I'm already doing with
>> >> the overlay support, then yes.  Otherwise, I want to stick with this
>> >> which is modelled after the i915 overlay interface.
>>
>> Oh i915 overlays aren't a good reason ;-) I've done those way back
>> when drm didn't have any plane infrastructure and pretty much as my
>> very contribution. So totally lacked any clue.
>>
>> If I can scrap together a bit of time I want to port the legacy
>> overlay code over to drm planes (and remap the current ioctl interface
>> to the drm plane interface for backwards compat). But atm that's
>> crushed under a giant pile of other todo items.
>
> Aren't we all :(  The amount of time I have to touch this has reduced
> substantially over the last couple of months, which is why there's been
> a few weeks between the RFC's for this.
>
> The issue here is that with the overlay interface, all I have to do
> with one of these pass-by-phys-address things which the X server gets
> is to:
>
> 1. associate the phys address with a gem object
> 2. pass it in via the overlay interface
>
> With the DRM plane stuff, this gets more complicated:
>
> 1. associate the phys address with a gem object
> 2. call another driver private ioctl to bind the gem object to a framebuffer
>    (there is no support for this in the generic ioctl API afaics) which
>    has to allocate and setup a framebuffer
> 3. use setplane to update the plane
>
> That all increases the expense of overlay, and adds further memory
> allocations to the path (and frees when the previous framebuffer is
> discarded.)
>
> That all makes for higher load due to more CPU expense.

Nothing prevents you from having a driver private ioctl on top of drm
plane to combine this all into one ioctl in addition to supporting the
standard plane interface

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 22:56                   ` Russell King - ARM Linux
@ 2013-06-10 23:17                     ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:17 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 6:56 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
>> I guess in the short term, the best I can think is keep those phys
>> ioctls as a small patch on top of the upstream driver.  It is ok to
>> leave place-holder ioctl #'s in the upstream driver so that the ioctl
>> #'s don't shift when you rebase.  And then try to get the vendor to
>> add support for dmabuf so that the patch on top of upstream can
>> eventually be dropped.  Maybe someone else has a better suggestion,
>> but I don't think we can merge those phys ioctls upstream, and I'd
>> really hate to 'throw the baby out with the bathwater' in this case
>> and not at least get the modesetting part of the driver merged.
>
> What you're saying is basically:
>
> 1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
>    on existing gstreamer, xbmc and other implementations without someone
>    rewriting all that code.
>
> 2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
>    the GEM objects to the GPU libraries in userspace, thereby preventing
>    any kind of GPU based acceleration.
>
> That makes the driver just be a dumb scanout only driver.  Sorry,
> that *really* does not interest me one bit, because the CPU doing
> framebuffer accesses is pig slow.

Well, yes that is basically what I am saying, unless we can find a
different way (dmabuf? if there is some way to pass it through the
blob bits you don't have src code for?)

The problem is that we really can't merge something upstream that lets
you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
otherwise if there is no other way, I hope someone will at least pick
up the modesetting parts of it and get that merged upstream.  The rest
of the driver is looking pretty good, and I think it would be useful
to have upstream.

BR,
-R

> There's really no point in such a driver; people might as well just
> use the standard fbdev driver instead.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:17                     ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:17 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 6:56 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
>> I guess in the short term, the best I can think is keep those phys
>> ioctls as a small patch on top of the upstream driver.  It is ok to
>> leave place-holder ioctl #'s in the upstream driver so that the ioctl
>> #'s don't shift when you rebase.  And then try to get the vendor to
>> add support for dmabuf so that the patch on top of upstream can
>> eventually be dropped.  Maybe someone else has a better suggestion,
>> but I don't think we can merge those phys ioctls upstream, and I'd
>> really hate to 'throw the baby out with the bathwater' in this case
>> and not at least get the modesetting part of the driver merged.
>
> What you're saying is basically:
>
> 1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
>    on existing gstreamer, xbmc and other implementations without someone
>    rewriting all that code.
>
> 2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
>    the GEM objects to the GPU libraries in userspace, thereby preventing
>    any kind of GPU based acceleration.
>
> That makes the driver just be a dumb scanout only driver.  Sorry,
> that *really* does not interest me one bit, because the CPU doing
> framebuffer accesses is pig slow.

Well, yes that is basically what I am saying, unless we can find a
different way (dmabuf? if there is some way to pass it through the
blob bits you don't have src code for?)

The problem is that we really can't merge something upstream that lets
you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
otherwise if there is no other way, I hope someone will at least pick
up the modesetting parts of it and get that merged upstream.  The rest
of the driver is looking pretty good, and I think it would be useful
to have upstream.

BR,
-R

> There's really no point in such a driver; people might as well just
> use the standard fbdev driver instead.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:17                     ` Rob Clark
@ 2013-06-10 23:24                       ` Dave Airlie
  -1 siblings, 0 replies; 120+ messages in thread
From: Dave Airlie @ 2013-06-10 23:24 UTC (permalink / raw)
  To: linux-arm-kernel

>>
>> That makes the driver just be a dumb scanout only driver.  Sorry,
>> that *really* does not interest me one bit, because the CPU doing
>> framebuffer accesses is pig slow.
>
> Well, yes that is basically what I am saying, unless we can find a
> different way (dmabuf? if there is some way to pass it through the
> blob bits you don't have src code for?)
>
> The problem is that we really can't merge something upstream that lets
> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
> otherwise if there is no other way, I hope someone will at least pick
> up the modesetting parts of it and get that merged upstream.  The rest
> of the driver is looking pretty good, and I think it would be useful
> to have upstream.

Totally what he said.

upstream shouldn't be restricted by the bad decisions of others, and
adding security bypasses is one of them.

I'd like to see all the ARM based drivers based on CMA if it can meet
their requirements
and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
come an unmaintainable
nightmare for everyone, but mostly for me.

Merging the base dumb KMS driver, then working out acceptable ways to
provide the extra
features is definitely the nicest way to do things.

Dave.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:24                       ` Dave Airlie
  0 siblings, 0 replies; 120+ messages in thread
From: Dave Airlie @ 2013-06-10 23:24 UTC (permalink / raw)
  To: Rob Clark
  Cc: linux-arm-kernel, Russell King - ARM Linux, Jason Cooper,
	dri-devel, Sebastian Hesselbarth

>>
>> That makes the driver just be a dumb scanout only driver.  Sorry,
>> that *really* does not interest me one bit, because the CPU doing
>> framebuffer accesses is pig slow.
>
> Well, yes that is basically what I am saying, unless we can find a
> different way (dmabuf? if there is some way to pass it through the
> blob bits you don't have src code for?)
>
> The problem is that we really can't merge something upstream that lets
> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
> otherwise if there is no other way, I hope someone will at least pick
> up the modesetting parts of it and get that merged upstream.  The rest
> of the driver is looking pretty good, and I think it would be useful
> to have upstream.

Totally what he said.

upstream shouldn't be restricted by the bad decisions of others, and
adding security bypasses is one of them.

I'd like to see all the ARM based drivers based on CMA if it can meet
their requirements
and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
come an unmaintainable
nightmare for everyone, but mostly for me.

Merging the base dumb KMS driver, then working out acceptable ways to
provide the extra
features is definitely the nicest way to do things.

Dave.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:24                       ` Dave Airlie
@ 2013-06-10 23:35                         ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:35 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 7:24 PM, Dave Airlie <airlied@gmail.com> wrote:
>>>
>>> That makes the driver just be a dumb scanout only driver.  Sorry,
>>> that *really* does not interest me one bit, because the CPU doing
>>> framebuffer accesses is pig slow.
>>
>> Well, yes that is basically what I am saying, unless we can find a
>> different way (dmabuf? if there is some way to pass it through the
>> blob bits you don't have src code for?)
>>
>> The problem is that we really can't merge something upstream that lets
>> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
>> otherwise if there is no other way, I hope someone will at least pick
>> up the modesetting parts of it and get that merged upstream.  The rest
>> of the driver is looking pretty good, and I think it would be useful
>> to have upstream.
>
> Totally what he said.
>
> upstream shouldn't be restricted by the bad decisions of others, and
> adding security bypasses is one of them.
>
> I'd like to see all the ARM based drivers based on CMA if it can meet
> their requirements

afaiu CMA has issues w/ changing cache attributes of pages on older
architectures like Russell is using.  Although perhaps on older arm
CMA should just be a straight carveout pool and not try to recycle the
unused pages.  At least then it would be the same API for the drivers.

BR,
-R

> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> come an unmaintainable
> nightmare for everyone, but mostly for me.
>
> Merging the base dumb KMS driver, then working out acceptable ways to
> provide the extra
> features is definitely the nicest way to do things.
>
> Dave.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:35                         ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:35 UTC (permalink / raw)
  To: Dave Airlie
  Cc: linux-arm-kernel, Russell King - ARM Linux, Jason Cooper,
	dri-devel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 7:24 PM, Dave Airlie <airlied@gmail.com> wrote:
>>>
>>> That makes the driver just be a dumb scanout only driver.  Sorry,
>>> that *really* does not interest me one bit, because the CPU doing
>>> framebuffer accesses is pig slow.
>>
>> Well, yes that is basically what I am saying, unless we can find a
>> different way (dmabuf? if there is some way to pass it through the
>> blob bits you don't have src code for?)
>>
>> The problem is that we really can't merge something upstream that lets
>> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
>> otherwise if there is no other way, I hope someone will at least pick
>> up the modesetting parts of it and get that merged upstream.  The rest
>> of the driver is looking pretty good, and I think it would be useful
>> to have upstream.
>
> Totally what he said.
>
> upstream shouldn't be restricted by the bad decisions of others, and
> adding security bypasses is one of them.
>
> I'd like to see all the ARM based drivers based on CMA if it can meet
> their requirements

afaiu CMA has issues w/ changing cache attributes of pages on older
architectures like Russell is using.  Although perhaps on older arm
CMA should just be a straight carveout pool and not try to recycle the
unused pages.  At least then it would be the same API for the drivers.

BR,
-R

> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> come an unmaintainable
> nightmare for everyone, but mostly for me.
>
> Merging the base dumb KMS driver, then working out acceptable ways to
> provide the extra
> features is definitely the nicest way to do things.
>
> Dave.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:24                       ` Dave Airlie
@ 2013-06-10 23:36                         ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 23:36 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
> I'd like to see all the ARM based drivers based on CMA if it can meet
> their requirements
> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> come an unmaintainable
> nightmare for everyone, but mostly for me.

I am *not* using the CMA layer - that layer is just plain broken in
DRM.  It forces every single gem object to be a CMA allocated object,
which means I can't have cacheable pixmaps in X.  And that makes X
suck.

Okay, I'm pulling this and I'm going to keep it in my private cubox
tree; I'm not persuing pushing this driver or any other Armada 510
driver into mainline anymore.  It's just too much fscking hastle
dealing with people who don't like various stuff.

I've done my best to clean a lot of the crap up, and the problem is
that no matter how much I clean up, it remains unacceptable.  Only
the 100% perfect solution seems to be acceptable.  That is
unacceptable given that this stuff has already consumed something
like 8 months solid of my time.

So no, I'm not wasting any further time on this crap.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:36                         ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 23:36 UTC (permalink / raw)
  To: Dave Airlie
  Cc: linux-arm-kernel, Rob Clark, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
> I'd like to see all the ARM based drivers based on CMA if it can meet
> their requirements
> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> come an unmaintainable
> nightmare for everyone, but mostly for me.

I am *not* using the CMA layer - that layer is just plain broken in
DRM.  It forces every single gem object to be a CMA allocated object,
which means I can't have cacheable pixmaps in X.  And that makes X
suck.

Okay, I'm pulling this and I'm going to keep it in my private cubox
tree; I'm not persuing pushing this driver or any other Armada 510
driver into mainline anymore.  It's just too much fscking hastle
dealing with people who don't like various stuff.

I've done my best to clean a lot of the crap up, and the problem is
that no matter how much I clean up, it remains unacceptable.  Only
the 100% perfect solution seems to be acceptable.  That is
unacceptable given that this stuff has already consumed something
like 8 months solid of my time.

So no, I'm not wasting any further time on this crap.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:17                     ` Rob Clark
@ 2013-06-10 23:38                       ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 23:38 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 07:17:22PM -0400, Rob Clark wrote:
> On Mon, Jun 10, 2013 at 6:56 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
> >> I guess in the short term, the best I can think is keep those phys
> >> ioctls as a small patch on top of the upstream driver.  It is ok to
> >> leave place-holder ioctl #'s in the upstream driver so that the ioctl
> >> #'s don't shift when you rebase.  And then try to get the vendor to
> >> add support for dmabuf so that the patch on top of upstream can
> >> eventually be dropped.  Maybe someone else has a better suggestion,
> >> but I don't think we can merge those phys ioctls upstream, and I'd
> >> really hate to 'throw the baby out with the bathwater' in this case
> >> and not at least get the modesetting part of the driver merged.
> >
> > What you're saying is basically:
> >
> > 1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
> >    on existing gstreamer, xbmc and other implementations without someone
> >    rewriting all that code.
> >
> > 2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
> >    the GEM objects to the GPU libraries in userspace, thereby preventing
> >    any kind of GPU based acceleration.
> >
> > That makes the driver just be a dumb scanout only driver.  Sorry,
> > that *really* does not interest me one bit, because the CPU doing
> > framebuffer accesses is pig slow.
> 
> Well, yes that is basically what I am saying, unless we can find a
> different way (dmabuf? if there is some way to pass it through the
> blob bits you don't have src code for?)
> 
> The problem is that we really can't merge something upstream that lets
> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or

Which bit of "THIS DRIVER DOESN'T DMA _TO_ ANY ADDRESS" did you not yet?

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:38                       ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 23:38 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jason Cooper, David Airlie, dri-devel, Darren Etheridge,
	linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 07:17:22PM -0400, Rob Clark wrote:
> On Mon, Jun 10, 2013 at 6:56 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
> >> I guess in the short term, the best I can think is keep those phys
> >> ioctls as a small patch on top of the upstream driver.  It is ok to
> >> leave place-holder ioctl #'s in the upstream driver so that the ioctl
> >> #'s don't shift when you rebase.  And then try to get the vendor to
> >> add support for dmabuf so that the patch on top of upstream can
> >> eventually be dropped.  Maybe someone else has a better suggestion,
> >> but I don't think we can merge those phys ioctls upstream, and I'd
> >> really hate to 'throw the baby out with the bathwater' in this case
> >> and not at least get the modesetting part of the driver merged.
> >
> > What you're saying is basically:
> >
> > 1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
> >    on existing gstreamer, xbmc and other implementations without someone
> >    rewriting all that code.
> >
> > 2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
> >    the GEM objects to the GPU libraries in userspace, thereby preventing
> >    any kind of GPU based acceleration.
> >
> > That makes the driver just be a dumb scanout only driver.  Sorry,
> > that *really* does not interest me one bit, because the CPU doing
> > framebuffer accesses is pig slow.
> 
> Well, yes that is basically what I am saying, unless we can find a
> different way (dmabuf? if there is some way to pass it through the
> blob bits you don't have src code for?)
> 
> The problem is that we really can't merge something upstream that lets
> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or

Which bit of "THIS DRIVER DOESN'T DMA _TO_ ANY ADDRESS" did you not yet?

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:36                         ` Russell King - ARM Linux
@ 2013-06-10 23:48                           ` Dave Airlie
  -1 siblings, 0 replies; 120+ messages in thread
From: Dave Airlie @ 2013-06-10 23:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
>> I'd like to see all the ARM based drivers based on CMA if it can meet
>> their requirements
>> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
>> come an unmaintainable
>> nightmare for everyone, but mostly for me.
>
> I am *not* using the CMA layer - that layer is just plain broken in
> DRM.  It forces every single gem object to be a CMA allocated object,
> which means I can't have cacheable pixmaps in X.  And that makes X
> suck.
>
> Okay, I'm pulling this and I'm going to keep it in my private cubox
> tree; I'm not persuing pushing this driver or any other Armada 510
> driver into mainline anymore.  It's just too much fscking hastle
> dealing with people who don't like various stuff.
>
> I've done my best to clean a lot of the crap up, and the problem is
> that no matter how much I clean up, it remains unacceptable.  Only
> the 100% perfect solution seems to be acceptable.  That is
> unacceptable given that this stuff has already consumed something
> like 8 months solid of my time.

Russell, aren't you a kernel maintainer, because for fuck sake get real.

I'm not merging bullshit into my tree that has a completely broken API that
has to be maintained for ever. You of all people should understand we
don't break Linux
userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
cleanups, its just broken, and I'll never merge it.

Dave.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:48                           ` Dave Airlie
  0 siblings, 0 replies; 120+ messages in thread
From: Dave Airlie @ 2013-06-10 23:48 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: linux-arm-kernel, Jason Cooper, dri-devel, Sebastian Hesselbarth

On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
>> I'd like to see all the ARM based drivers based on CMA if it can meet
>> their requirements
>> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
>> come an unmaintainable
>> nightmare for everyone, but mostly for me.
>
> I am *not* using the CMA layer - that layer is just plain broken in
> DRM.  It forces every single gem object to be a CMA allocated object,
> which means I can't have cacheable pixmaps in X.  And that makes X
> suck.
>
> Okay, I'm pulling this and I'm going to keep it in my private cubox
> tree; I'm not persuing pushing this driver or any other Armada 510
> driver into mainline anymore.  It's just too much fscking hastle
> dealing with people who don't like various stuff.
>
> I've done my best to clean a lot of the crap up, and the problem is
> that no matter how much I clean up, it remains unacceptable.  Only
> the 100% perfect solution seems to be acceptable.  That is
> unacceptable given that this stuff has already consumed something
> like 8 months solid of my time.

Russell, aren't you a kernel maintainer, because for fuck sake get real.

I'm not merging bullshit into my tree that has a completely broken API that
has to be maintained for ever. You of all people should understand we
don't break Linux
userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
cleanups, its just broken, and I'll never merge it.

Dave.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:38                       ` Russell King - ARM Linux
@ 2013-06-10 23:49                         ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:49 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 7:38 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 07:17:22PM -0400, Rob Clark wrote:
>> On Mon, Jun 10, 2013 at 6:56 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
>> >> I guess in the short term, the best I can think is keep those phys
>> >> ioctls as a small patch on top of the upstream driver.  It is ok to
>> >> leave place-holder ioctl #'s in the upstream driver so that the ioctl
>> >> #'s don't shift when you rebase.  And then try to get the vendor to
>> >> add support for dmabuf so that the patch on top of upstream can
>> >> eventually be dropped.  Maybe someone else has a better suggestion,
>> >> but I don't think we can merge those phys ioctls upstream, and I'd
>> >> really hate to 'throw the baby out with the bathwater' in this case
>> >> and not at least get the modesetting part of the driver merged.
>> >
>> > What you're saying is basically:
>> >
>> > 1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
>> >    on existing gstreamer, xbmc and other implementations without someone
>> >    rewriting all that code.
>> >
>> > 2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
>> >    the GEM objects to the GPU libraries in userspace, thereby preventing
>> >    any kind of GPU based acceleration.
>> >
>> > That makes the driver just be a dumb scanout only driver.  Sorry,
>> > that *really* does not interest me one bit, because the CPU doing
>> > framebuffer accesses is pig slow.
>>
>> Well, yes that is basically what I am saying, unless we can find a
>> different way (dmabuf? if there is some way to pass it through the
>> blob bits you don't have src code for?)
>>
>> The problem is that we really can't merge something upstream that lets
>> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
>
> Which bit of "THIS DRIVER DOESN'T DMA _TO_ ANY ADDRESS" did you not yet?

ok, I see the 'put_image' ioctl is a non-destructive overlay (ie. not
a blit to memory).  In which case it is limited to letting you just
scan out arbitrary memory.  Which is still not awesome, but not nearly
as bad.

And fwiw, CMA is not a blocker point for me.  Although it seems like a
good idea into trying to talk someone into making CMA do something
sane on older arm's so that at some point armada driver can use it.

You probably can't use the drm cma helpers (since that wouldn't let
you do cached buffers), so the change to use dma_alloc_* from your
driver at a later date would be pretty small.  Fwiw, omapdrm and I
think also exynos both use CMA for some buffers (for example, omap3 or
anything without dmm/tiler would use cma for scanout buffers).

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:49                         ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-10 23:49 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 7:38 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Mon, Jun 10, 2013 at 07:17:22PM -0400, Rob Clark wrote:
>> On Mon, Jun 10, 2013 at 6:56 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > On Mon, Jun 10, 2013 at 06:49:06PM -0400, Rob Clark wrote:
>> >> I guess in the short term, the best I can think is keep those phys
>> >> ioctls as a small patch on top of the upstream driver.  It is ok to
>> >> leave place-holder ioctl #'s in the upstream driver so that the ioctl
>> >> #'s don't shift when you rebase.  And then try to get the vendor to
>> >> add support for dmabuf so that the patch on top of upstream can
>> >> eventually be dropped.  Maybe someone else has a better suggestion,
>> >> but I don't think we can merge those phys ioctls upstream, and I'd
>> >> really hate to 'throw the baby out with the bathwater' in this case
>> >> and not at least get the modesetting part of the driver merged.
>> >
>> > What you're saying is basically:
>> >
>> > 1. Throw out DRM_ARMADA_GEM_CREATE_PHYS, which cripples video playback
>> >    on existing gstreamer, xbmc and other implementations without someone
>> >    rewriting all that code.
>> >
>> > 2. Throw out DRM_ARMADA_GEM_PROP, which prevents any form of passing
>> >    the GEM objects to the GPU libraries in userspace, thereby preventing
>> >    any kind of GPU based acceleration.
>> >
>> > That makes the driver just be a dumb scanout only driver.  Sorry,
>> > that *really* does not interest me one bit, because the CPU doing
>> > framebuffer accesses is pig slow.
>>
>> Well, yes that is basically what I am saying, unless we can find a
>> different way (dmabuf? if there is some way to pass it through the
>> blob bits you don't have src code for?)
>>
>> The problem is that we really can't merge something upstream that lets
>> you dma to arbitrary physical address.  Maybe in staging, perhaps?  Or
>
> Which bit of "THIS DRIVER DOESN'T DMA _TO_ ANY ADDRESS" did you not yet?

ok, I see the 'put_image' ioctl is a non-destructive overlay (ie. not
a blit to memory).  In which case it is limited to letting you just
scan out arbitrary memory.  Which is still not awesome, but not nearly
as bad.

And fwiw, CMA is not a blocker point for me.  Although it seems like a
good idea into trying to talk someone into making CMA do something
sane on older arm's so that at some point armada driver can use it.

You probably can't use the drm cma helpers (since that wouldn't let
you do cached buffers), so the change to use dma_alloc_* from your
driver at a later date would be pretty small.  Fwiw, omapdrm and I
think also exynos both use CMA for some buffers (for example, omap3 or
anything without dmm/tiler would use cma for scanout buffers).

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:48                           ` Dave Airlie
@ 2013-06-10 23:56                             ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 23:56 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 11, 2013 at 09:48:57AM +1000, Dave Airlie wrote:
> On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
> >> I'd like to see all the ARM based drivers based on CMA if it can meet
> >> their requirements
> >> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> >> come an unmaintainable
> >> nightmare for everyone, but mostly for me.
> >
> > I am *not* using the CMA layer - that layer is just plain broken in
> > DRM.  It forces every single gem object to be a CMA allocated object,
> > which means I can't have cacheable pixmaps in X.  And that makes X
> > suck.
> >
> > Okay, I'm pulling this and I'm going to keep it in my private cubox
> > tree; I'm not persuing pushing this driver or any other Armada 510
> > driver into mainline anymore.  It's just too much fscking hastle
> > dealing with people who don't like various stuff.
> >
> > I've done my best to clean a lot of the crap up, and the problem is
> > that no matter how much I clean up, it remains unacceptable.  Only
> > the 100% perfect solution seems to be acceptable.  That is
> > unacceptable given that this stuff has already consumed something
> > like 8 months solid of my time.
> 
> Russell, aren't you a kernel maintainer, because for fuck sake get real.
> 
> I'm not merging bullshit into my tree that has a completely broken API that
> has to be maintained for ever. You of all people should understand we
> don't break Linux
> userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
> cleanups, its just broken, and I'll never merge it.

As I say, I'm no longer interested in pushing this into mainline.  I've
said my piece above, and I'm not changing that.  I'm not spending years
trying to sort this stuff out, by which time the platforms using it will
be long since obsolete.  The "8 months" is not an exaggeration.  That's
the time taken to sort out the kernel side, the libdrm stuff, the
X server driver, and get video decode working on these platforms with
vlc.

It works for me, and that's really at this point all I care about.

And yes, I'm a kernel maintainer.  A FUCKING busy one right now at that.
I haven't stopped working since 9am today yet, and it's now 1am.  Do the
maths.  I really wish I had some time to myself now to think about some
of these bigger issues which this has brought up, but I don't.

And no, I *never* said that adding a phys address was a cleanup.

Sheesh why don't you read my emails properly.  Another reason why I
won't be continuing this discussion much further.

I said that _this_ amount of effort is a result of doing *LOTS* of
cleanups.  A DRM driver for this hardware *didn't* exist until I wrote
it.  There was a fbdev driver before, which was passed phys addresses
there for the overlay, and passed phys addresses back out after the
overlay frame has been replaced.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-10 23:56                             ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-10 23:56 UTC (permalink / raw)
  To: Dave Airlie
  Cc: linux-arm-kernel, Rob Clark, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Tue, Jun 11, 2013 at 09:48:57AM +1000, Dave Airlie wrote:
> On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
> >> I'd like to see all the ARM based drivers based on CMA if it can meet
> >> their requirements
> >> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> >> come an unmaintainable
> >> nightmare for everyone, but mostly for me.
> >
> > I am *not* using the CMA layer - that layer is just plain broken in
> > DRM.  It forces every single gem object to be a CMA allocated object,
> > which means I can't have cacheable pixmaps in X.  And that makes X
> > suck.
> >
> > Okay, I'm pulling this and I'm going to keep it in my private cubox
> > tree; I'm not persuing pushing this driver or any other Armada 510
> > driver into mainline anymore.  It's just too much fscking hastle
> > dealing with people who don't like various stuff.
> >
> > I've done my best to clean a lot of the crap up, and the problem is
> > that no matter how much I clean up, it remains unacceptable.  Only
> > the 100% perfect solution seems to be acceptable.  That is
> > unacceptable given that this stuff has already consumed something
> > like 8 months solid of my time.
> 
> Russell, aren't you a kernel maintainer, because for fuck sake get real.
> 
> I'm not merging bullshit into my tree that has a completely broken API that
> has to be maintained for ever. You of all people should understand we
> don't break Linux
> userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
> cleanups, its just broken, and I'll never merge it.

As I say, I'm no longer interested in pushing this into mainline.  I've
said my piece above, and I'm not changing that.  I'm not spending years
trying to sort this stuff out, by which time the platforms using it will
be long since obsolete.  The "8 months" is not an exaggeration.  That's
the time taken to sort out the kernel side, the libdrm stuff, the
X server driver, and get video decode working on these platforms with
vlc.

It works for me, and that's really at this point all I care about.

And yes, I'm a kernel maintainer.  A FUCKING busy one right now at that.
I haven't stopped working since 9am today yet, and it's now 1am.  Do the
maths.  I really wish I had some time to myself now to think about some
of these bigger issues which this has brought up, but I don't.

And no, I *never* said that adding a phys address was a cleanup.

Sheesh why don't you read my emails properly.  Another reason why I
won't be continuing this discussion much further.

I said that _this_ amount of effort is a result of doing *LOTS* of
cleanups.  A DRM driver for this hardware *didn't* exist until I wrote
it.  There was a fbdev driver before, which was passed phys addresses
there for the overlay, and passed phys addresses back out after the
overlay frame has been replaced.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 22:32             ` Russell King - ARM Linux
@ 2013-06-11  7:33               ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-11  7:33 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 11:32:54PM +0100, Russell King - ARM Linux wrote:
> On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
> > On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> > > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> > > <linux@arm.linux.org.uk> wrote:
> > >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> > >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> > >>> <rmk+kernel@arm.linux.org.uk> wrote:
> > >>> > This patch adds support for the pair of LCD controllers on the Marvell
> > >>> > Armada 510 SoCs.  This driver supports:
> > >>> > - multiple contiguous scanout buffers for video and graphics
> > >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> > >>> >   acceleration
> > >>> > - dual lcd0 and lcd1 crt operation
> > >>> > - video overlay on each LCD crt
> > >>>
> > >>> Any particular reason for not exposing the overlays as drm_plane's?
> > >>> That is probably something that should change before merging the
> > >>> driver.
> > >>
> > >> Only through not understanding much of DRM when I started this.
> > >> Provided DRM planes can do everything that I'm already doing with
> > >> the overlay support, then yes.  Otherwise, I want to stick with this
> > >> which is modelled after the i915 overlay interface.
> > 
> > Oh i915 overlays aren't a good reason ;-) I've done those way back
> > when drm didn't have any plane infrastructure and pretty much as my
> > very contribution. So totally lacked any clue.
> > 
> > If I can scrap together a bit of time I want to port the legacy
> > overlay code over to drm planes (and remap the current ioctl interface
> > to the drm plane interface for backwards compat). But atm that's
> > crushed under a giant pile of other todo items.
> 
> Aren't we all :(  The amount of time I have to touch this has reduced
> substantially over the last couple of months, which is why there's been
> a few weeks between the RFC's for this.
> 
> The issue here is that with the overlay interface, all I have to do
> with one of these pass-by-phys-address things which the X server gets
> is to:
> 
> 1. associate the phys address with a gem object
> 2. pass it in via the overlay interface
> 
> With the DRM plane stuff, this gets more complicated:
> 
> 1. associate the phys address with a gem object
> 2. call another driver private ioctl to bind the gem object to a framebuffer
>    (there is no support for this in the generic ioctl API afaics) which
>    has to allocate and setup a framebuffer

drm_mode_addfb2 is the ioctl which packs up a bunch of driver objects
(lookup is done in the driver callback, so could in theory be something
else than gem) and wraps them up into a fb with a pile of metadata
(per-plane stride&offset, fourcc code, ...). drm_mode_addfb is the legacy
ioctl for simple packed rgb formats. The drm core has helpers to map
between the two kinds of metadata (as far as possible).

> 3. use setplane to update the plane
> 
> That all increases the expense of overlay, and adds further memory
> allocations to the path (and frees when the previous framebuffer is
> discarded.)
> 
> That all makes for higher load due to more CPU expense.

CPU load won't kill you for these, the problem was more that up to 3.8 the
locking was a bit b0rked and background probing (e.g. the output poll work
reading an edid) could block framebuffer creation and pageflips. That
should be all fixed no in 3.9 (with the execption of set_planes, for those
I'd like to use the ww mutexes ... but could also be fixed now with a few
hacks).

The addfb overhead should also be easy to amortize (if it really hurts
you) as long as you keep a queue of buffers around (like X/wayland do).
Then it's even better for planar since you avoid to do per-plane lookup
;-)
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-11  7:33               ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-11  7:33 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Mon, Jun 10, 2013 at 11:32:54PM +0100, Russell King - ARM Linux wrote:
> On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
> > On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> > > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> > > <linux@arm.linux.org.uk> wrote:
> > >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> > >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> > >>> <rmk+kernel@arm.linux.org.uk> wrote:
> > >>> > This patch adds support for the pair of LCD controllers on the Marvell
> > >>> > Armada 510 SoCs.  This driver supports:
> > >>> > - multiple contiguous scanout buffers for video and graphics
> > >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> > >>> >   acceleration
> > >>> > - dual lcd0 and lcd1 crt operation
> > >>> > - video overlay on each LCD crt
> > >>>
> > >>> Any particular reason for not exposing the overlays as drm_plane's?
> > >>> That is probably something that should change before merging the
> > >>> driver.
> > >>
> > >> Only through not understanding much of DRM when I started this.
> > >> Provided DRM planes can do everything that I'm already doing with
> > >> the overlay support, then yes.  Otherwise, I want to stick with this
> > >> which is modelled after the i915 overlay interface.
> > 
> > Oh i915 overlays aren't a good reason ;-) I've done those way back
> > when drm didn't have any plane infrastructure and pretty much as my
> > very contribution. So totally lacked any clue.
> > 
> > If I can scrap together a bit of time I want to port the legacy
> > overlay code over to drm planes (and remap the current ioctl interface
> > to the drm plane interface for backwards compat). But atm that's
> > crushed under a giant pile of other todo items.
> 
> Aren't we all :(  The amount of time I have to touch this has reduced
> substantially over the last couple of months, which is why there's been
> a few weeks between the RFC's for this.
> 
> The issue here is that with the overlay interface, all I have to do
> with one of these pass-by-phys-address things which the X server gets
> is to:
> 
> 1. associate the phys address with a gem object
> 2. pass it in via the overlay interface
> 
> With the DRM plane stuff, this gets more complicated:
> 
> 1. associate the phys address with a gem object
> 2. call another driver private ioctl to bind the gem object to a framebuffer
>    (there is no support for this in the generic ioctl API afaics) which
>    has to allocate and setup a framebuffer

drm_mode_addfb2 is the ioctl which packs up a bunch of driver objects
(lookup is done in the driver callback, so could in theory be something
else than gem) and wraps them up into a fb with a pile of metadata
(per-plane stride&offset, fourcc code, ...). drm_mode_addfb is the legacy
ioctl for simple packed rgb formats. The drm core has helpers to map
between the two kinds of metadata (as far as possible).

> 3. use setplane to update the plane
> 
> That all increases the expense of overlay, and adds further memory
> allocations to the path (and frees when the previous framebuffer is
> discarded.)
> 
> That all makes for higher load due to more CPU expense.

CPU load won't kill you for these, the problem was more that up to 3.8 the
locking was a bit b0rked and background probing (e.g. the output poll work
reading an edid) could block framebuffer creation and pageflips. That
should be all fixed no in 3.9 (with the execption of set_planes, for those
I'd like to use the ww mutexes ... but could also be fixed now with a few
hacks).

The addfb overhead should also be easy to amortize (if it really hurts
you) as long as you keep a queue of buffers around (like X/wayland do).
Then it's even better for planar since you avoid to do per-plane lookup
;-)
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 22:01           ` Daniel Vetter
@ 2013-06-11  8:08             ` Ville Syrjälä
  -1 siblings, 0 replies; 120+ messages in thread
From: Ville Syrjälä @ 2013-06-11  8:08 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
> On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> > <linux@arm.linux.org.uk> wrote:
> >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> >>> <rmk+kernel@arm.linux.org.uk> wrote:
> >>> > This patch adds support for the pair of LCD controllers on the Marvell
> >>> > Armada 510 SoCs.  This driver supports:
> >>> > - multiple contiguous scanout buffers for video and graphics
> >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> >>> >   acceleration
> >>> > - dual lcd0 and lcd1 crt operation
> >>> > - video overlay on each LCD crt
> >>>
> >>> Any particular reason for not exposing the overlays as drm_plane's?
> >>> That is probably something that should change before merging the
> >>> driver.
> >>
> >> Only through not understanding much of DRM when I started this.
> >> Provided DRM planes can do everything that I'm already doing with
> >> the overlay support, then yes.  Otherwise, I want to stick with this
> >> which is modelled after the i915 overlay interface.
> 
> Oh i915 overlays aren't a good reason ;-) I've done those way back
> when drm didn't have any plane infrastructure and pretty much as my
> very contribution. So totally lacked any clue.
> 
> If I can scrap together a bit of time I want to port the legacy
> overlay code over to drm planes (and remap the current ioctl interface
> to the drm plane interface for backwards compat). But atm that's
> crushed under a giant pile of other todo items.

I hope you remember the mdfld overlay code I posted a long time ago:
http://lists.freedesktop.org/archives/intel-gfx/2012-September/020479.html

I don't think it's too far from being operational on gen.

-- 
Ville Syrj?l?
Intel OTC

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-11  8:08             ` Ville Syrjälä
  0 siblings, 0 replies; 120+ messages in thread
From: Ville Syrjälä @ 2013-06-11  8:08 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Russell King - ARM Linux, Jason Cooper, dri-devel, Rob Clark,
	linux-arm-kernel, Sebastian Hesselbarth

On Tue, Jun 11, 2013 at 12:01:59AM +0200, Daniel Vetter wrote:
> On Mon, Jun 10, 2013 at 9:59 PM, Rob Clark <robdclark@gmail.com> wrote:
> > On Mon, Jun 10, 2013 at 1:06 PM, Russell King - ARM Linux
> > <linux@arm.linux.org.uk> wrote:
> >> On Mon, Jun 10, 2013 at 11:57:32AM -0400, Rob Clark wrote:
> >>> On Sun, Jun 9, 2013 at 3:29 PM, Russell King
> >>> <rmk+kernel@arm.linux.org.uk> wrote:
> >>> > This patch adds support for the pair of LCD controllers on the Marvell
> >>> > Armada 510 SoCs.  This driver supports:
> >>> > - multiple contiguous scanout buffers for video and graphics
> >>> > - shm backed cacheable buffer objects for X pixmaps for Vivante GPU
> >>> >   acceleration
> >>> > - dual lcd0 and lcd1 crt operation
> >>> > - video overlay on each LCD crt
> >>>
> >>> Any particular reason for not exposing the overlays as drm_plane's?
> >>> That is probably something that should change before merging the
> >>> driver.
> >>
> >> Only through not understanding much of DRM when I started this.
> >> Provided DRM planes can do everything that I'm already doing with
> >> the overlay support, then yes.  Otherwise, I want to stick with this
> >> which is modelled after the i915 overlay interface.
> 
> Oh i915 overlays aren't a good reason ;-) I've done those way back
> when drm didn't have any plane infrastructure and pretty much as my
> very contribution. So totally lacked any clue.
> 
> If I can scrap together a bit of time I want to port the legacy
> overlay code over to drm planes (and remap the current ioctl interface
> to the drm plane interface for backwards compat). But atm that's
> crushed under a giant pile of other todo items.

I hope you remember the mdfld overlay code I posted a long time ago:
http://lists.freedesktop.org/archives/intel-gfx/2012-September/020479.html

I don't think it's too far from being operational on gen.

-- 
Ville Syrjälä
Intel OTC

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-10 23:48                           ` Dave Airlie
@ 2013-06-12 13:48                             ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 13:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 11, 2013 at 09:48:57AM +1000, Dave Airlie wrote:
> On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
> >> I'd like to see all the ARM based drivers based on CMA if it can meet
> >> their requirements
> >> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> >> come an unmaintainable
> >> nightmare for everyone, but mostly for me.
> >
> > I am *not* using the CMA layer - that layer is just plain broken in
> > DRM.  It forces every single gem object to be a CMA allocated object,
> > which means I can't have cacheable pixmaps in X.  And that makes X
> > suck.
> >
> > Okay, I'm pulling this and I'm going to keep it in my private cubox
> > tree; I'm not persuing pushing this driver or any other Armada 510
> > driver into mainline anymore.  It's just too much fscking hastle
> > dealing with people who don't like various stuff.
> >
> > I've done my best to clean a lot of the crap up, and the problem is
> > that no matter how much I clean up, it remains unacceptable.  Only
> > the 100% perfect solution seems to be acceptable.  That is
> > unacceptable given that this stuff has already consumed something
> > like 8 months solid of my time.
> 
> Russell, aren't you a kernel maintainer, because for fuck sake get real.
> 
> I'm not merging bullshit into my tree that has a completely broken API that
> has to be maintained for ever. You of all people should understand we
> don't break Linux
> userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
> cleanups, its just broken, and I'll never merge it.

And having thought about this driver, DRM some more, I'm now of the
opinion that DRM is not suitable for driving hardware where the GPU is
an entirely separate IP block from the display side.

DRM is modelled after the PC setup where your "graphics card" does
everything - it has the GPU, display and connectors all integrated
together.  This is not the case on embedded SoCs, which can be a
collection of different IPs all integrated together.

DRM is based on the assumption that you have a single card and everything
is known about that card.  Again, this is not the case with embedded SoCs,
which is why Sebastian is having a hard time with the DRM slave encoder
stuff.

If DRM is going to be usable on SoCs, it needs to become more modular in
nature, allowing the same scanout stuff to be used with different GPUs
and providing _kernel_ side interfaces to allow different GPUs to be
plugged in to a scanout implementation, or vice versa (the reverse is
probably easier because the scanout interface is nicely abstracted).

Or we go off and write an entirely new subsystem which *does* suit the
needs of modular SoC implementations.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 13:48                             ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 13:48 UTC (permalink / raw)
  To: Dave Airlie
  Cc: linux-arm-kernel, Rob Clark, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Tue, Jun 11, 2013 at 09:48:57AM +1000, Dave Airlie wrote:
> On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
> >> I'd like to see all the ARM based drivers based on CMA if it can meet
> >> their requirements
> >> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
> >> come an unmaintainable
> >> nightmare for everyone, but mostly for me.
> >
> > I am *not* using the CMA layer - that layer is just plain broken in
> > DRM.  It forces every single gem object to be a CMA allocated object,
> > which means I can't have cacheable pixmaps in X.  And that makes X
> > suck.
> >
> > Okay, I'm pulling this and I'm going to keep it in my private cubox
> > tree; I'm not persuing pushing this driver or any other Armada 510
> > driver into mainline anymore.  It's just too much fscking hastle
> > dealing with people who don't like various stuff.
> >
> > I've done my best to clean a lot of the crap up, and the problem is
> > that no matter how much I clean up, it remains unacceptable.  Only
> > the 100% perfect solution seems to be acceptable.  That is
> > unacceptable given that this stuff has already consumed something
> > like 8 months solid of my time.
> 
> Russell, aren't you a kernel maintainer, because for fuck sake get real.
> 
> I'm not merging bullshit into my tree that has a completely broken API that
> has to be maintained for ever. You of all people should understand we
> don't break Linux
> userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
> cleanups, its just broken, and I'll never merge it.

And having thought about this driver, DRM some more, I'm now of the
opinion that DRM is not suitable for driving hardware where the GPU is
an entirely separate IP block from the display side.

DRM is modelled after the PC setup where your "graphics card" does
everything - it has the GPU, display and connectors all integrated
together.  This is not the case on embedded SoCs, which can be a
collection of different IPs all integrated together.

DRM is based on the assumption that you have a single card and everything
is known about that card.  Again, this is not the case with embedded SoCs,
which is why Sebastian is having a hard time with the DRM slave encoder
stuff.

If DRM is going to be usable on SoCs, it needs to become more modular in
nature, allowing the same scanout stuff to be used with different GPUs
and providing _kernel_ side interfaces to allow different GPUs to be
plugged in to a scanout implementation, or vice versa (the reverse is
probably easier because the scanout interface is nicely abstracted).

Or we go off and write an entirely new subsystem which *does* suit the
needs of modular SoC implementations.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 13:48                             ` Russell King - ARM Linux
@ 2013-06-12 13:56                               ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-12 13:56 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, Jun 11, 2013 at 09:48:57AM +1000, Dave Airlie wrote:
>> On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
>> >> I'd like to see all the ARM based drivers based on CMA if it can meet
>> >> their requirements
>> >> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
>> >> come an unmaintainable
>> >> nightmare for everyone, but mostly for me.
>> >
>> > I am *not* using the CMA layer - that layer is just plain broken in
>> > DRM.  It forces every single gem object to be a CMA allocated object,
>> > which means I can't have cacheable pixmaps in X.  And that makes X
>> > suck.
>> >
>> > Okay, I'm pulling this and I'm going to keep it in my private cubox
>> > tree; I'm not persuing pushing this driver or any other Armada 510
>> > driver into mainline anymore.  It's just too much fscking hastle
>> > dealing with people who don't like various stuff.
>> >
>> > I've done my best to clean a lot of the crap up, and the problem is
>> > that no matter how much I clean up, it remains unacceptable.  Only
>> > the 100% perfect solution seems to be acceptable.  That is
>> > unacceptable given that this stuff has already consumed something
>> > like 8 months solid of my time.
>>
>> Russell, aren't you a kernel maintainer, because for fuck sake get real.
>>
>> I'm not merging bullshit into my tree that has a completely broken API that
>> has to be maintained for ever. You of all people should understand we
>> don't break Linux
>> userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
>> cleanups, its just broken, and I'll never merge it.
>
> And having thought about this driver, DRM some more, I'm now of the
> opinion that DRM is not suitable for driving hardware where the GPU is
> an entirely separate IP block from the display side.
>
> DRM is modelled after the PC setup where your "graphics card" does
> everything - it has the GPU, display and connectors all integrated
> together.  This is not the case on embedded SoCs, which can be a
> collection of different IPs all integrated together.

actually it isn't even the case on desktop/laptop anymore, where you
can have one gpu with scanout and a second one without (or just with
display controller not hooked up to anything, etc, etc)

That is the point of dmabuf and the upcoming fence/reservation stuff.

BR,
-R

> DRM is based on the assumption that you have a single card and everything
> is known about that card.  Again, this is not the case with embedded SoCs,
> which is why Sebastian is having a hard time with the DRM slave encoder
> stuff.
>
> If DRM is going to be usable on SoCs, it needs to become more modular in
> nature, allowing the same scanout stuff to be used with different GPUs
> and providing _kernel_ side interfaces to allow different GPUs to be
> plugged in to a scanout implementation, or vice versa (the reverse is
> probably easier because the scanout interface is nicely abstracted).
>
> Or we go off and write an entirely new subsystem which *does* suit the
> needs of modular SoC implementations.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 13:56                               ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-12 13:56 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: linux-arm-kernel, Jason Cooper, dri-devel, Sebastian Hesselbarth

On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, Jun 11, 2013 at 09:48:57AM +1000, Dave Airlie wrote:
>> On Tue, Jun 11, 2013 at 9:36 AM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > On Tue, Jun 11, 2013 at 09:24:16AM +1000, Dave Airlie wrote:
>> >> I'd like to see all the ARM based drivers based on CMA if it can meet
>> >> their requirements
>> >> and using close to standard GEM/dma-buf interfaces. Otherwise it'll be
>> >> come an unmaintainable
>> >> nightmare for everyone, but mostly for me.
>> >
>> > I am *not* using the CMA layer - that layer is just plain broken in
>> > DRM.  It forces every single gem object to be a CMA allocated object,
>> > which means I can't have cacheable pixmaps in X.  And that makes X
>> > suck.
>> >
>> > Okay, I'm pulling this and I'm going to keep it in my private cubox
>> > tree; I'm not persuing pushing this driver or any other Armada 510
>> > driver into mainline anymore.  It's just too much fscking hastle
>> > dealing with people who don't like various stuff.
>> >
>> > I've done my best to clean a lot of the crap up, and the problem is
>> > that no matter how much I clean up, it remains unacceptable.  Only
>> > the 100% perfect solution seems to be acceptable.  That is
>> > unacceptable given that this stuff has already consumed something
>> > like 8 months solid of my time.
>>
>> Russell, aren't you a kernel maintainer, because for fuck sake get real.
>>
>> I'm not merging bullshit into my tree that has a completely broken API that
>> has to be maintained for ever. You of all people should understand we
>> don't break Linux
>> userspace APIs, and adding a phys addr one is wrong, wrong, wrong, its not
>> cleanups, its just broken, and I'll never merge it.
>
> And having thought about this driver, DRM some more, I'm now of the
> opinion that DRM is not suitable for driving hardware where the GPU is
> an entirely separate IP block from the display side.
>
> DRM is modelled after the PC setup where your "graphics card" does
> everything - it has the GPU, display and connectors all integrated
> together.  This is not the case on embedded SoCs, which can be a
> collection of different IPs all integrated together.

actually it isn't even the case on desktop/laptop anymore, where you
can have one gpu with scanout and a second one without (or just with
display controller not hooked up to anything, etc, etc)

That is the point of dmabuf and the upcoming fence/reservation stuff.

BR,
-R

> DRM is based on the assumption that you have a single card and everything
> is known about that card.  Again, this is not the case with embedded SoCs,
> which is why Sebastian is having a hard time with the DRM slave encoder
> stuff.
>
> If DRM is going to be usable on SoCs, it needs to become more modular in
> nature, allowing the same scanout stuff to be used with different GPUs
> and providing _kernel_ side interfaces to allow different GPUs to be
> plugged in to a scanout implementation, or vice versa (the reverse is
> probably easier because the scanout interface is nicely abstracted).
>
> Or we go off and write an entirely new subsystem which *does* suit the
> needs of modular SoC implementations.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 13:56                               ` Rob Clark
@ 2013-06-12 16:49                                 ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 16:49 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
> On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > And having thought about this driver, DRM some more, I'm now of the
> > opinion that DRM is not suitable for driving hardware where the GPU is
> > an entirely separate IP block from the display side.
> >
> > DRM is modelled after the PC setup where your "graphics card" does
> > everything - it has the GPU, display and connectors all integrated
> > together.  This is not the case on embedded SoCs, which can be a
> > collection of different IPs all integrated together.
> 
> actually it isn't even the case on desktop/laptop anymore, where you
> can have one gpu with scanout and a second one without (or just with
> display controller not hooked up to anything, etc, etc)
> 
> That is the point of dmabuf and the upcoming fence/reservation stuff.

Okay, but dmabuf really needs to be fixed, because as it stands this API
is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
to have its return value ignored - mappings can fail, and (b) the returned
number indicates how many entries are valid for the _mapped_ version of
the scatterlist.

Both these points are important if your DMA API implementation uses an
IOMMU, which may coalesce the scatterlist array when creating mappings -
much as described in Documentation/DMA-API.txt and
Documentation/DMA-API-HOWTO.txt.

That is not all DRMs fault - (a) is attributable to DRM's prime
implementation:

        sgt = obj->dev->driver->gem_prime_get_sg_table(obj);

        if (!IS_ERR_OR_NULL(sgt))
                dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);

and quite why it does the dma_map_sg() beneath the struct_mutex is
beyond me - if the array of pages isn't safe without the mutex being
held, then it isn't safe after the dma_map_sg() operation has completed
and the mutex has been released.

However, (b) is more a problem for dmabuf (which I just rather aptly
mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
to return the value from dma_map_sg(), or it needs to stop requiring
this of the .map_dma_buf method and have it done by the caller of
this method so the caller can have access to that returned value.

Added Sumit Semwal to this email for the dmabuf issue.

Thankfully, this being correct isn't a requirement for me, but it's
something which _should_ be fixed.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 16:49                                 ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 16:49 UTC (permalink / raw)
  To: Rob Clark, Sumit Semwal
  Cc: linux-arm-kernel, Dave Airlie, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
> On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > And having thought about this driver, DRM some more, I'm now of the
> > opinion that DRM is not suitable for driving hardware where the GPU is
> > an entirely separate IP block from the display side.
> >
> > DRM is modelled after the PC setup where your "graphics card" does
> > everything - it has the GPU, display and connectors all integrated
> > together.  This is not the case on embedded SoCs, which can be a
> > collection of different IPs all integrated together.
> 
> actually it isn't even the case on desktop/laptop anymore, where you
> can have one gpu with scanout and a second one without (or just with
> display controller not hooked up to anything, etc, etc)
> 
> That is the point of dmabuf and the upcoming fence/reservation stuff.

Okay, but dmabuf really needs to be fixed, because as it stands this API
is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
to have its return value ignored - mappings can fail, and (b) the returned
number indicates how many entries are valid for the _mapped_ version of
the scatterlist.

Both these points are important if your DMA API implementation uses an
IOMMU, which may coalesce the scatterlist array when creating mappings -
much as described in Documentation/DMA-API.txt and
Documentation/DMA-API-HOWTO.txt.

That is not all DRMs fault - (a) is attributable to DRM's prime
implementation:

        sgt = obj->dev->driver->gem_prime_get_sg_table(obj);

        if (!IS_ERR_OR_NULL(sgt))
                dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);

and quite why it does the dma_map_sg() beneath the struct_mutex is
beyond me - if the array of pages isn't safe without the mutex being
held, then it isn't safe after the dma_map_sg() operation has completed
and the mutex has been released.

However, (b) is more a problem for dmabuf (which I just rather aptly
mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
to return the value from dma_map_sg(), or it needs to stop requiring
this of the .map_dma_buf method and have it done by the caller of
this method so the caller can have access to that returned value.

Added Sumit Semwal to this email for the dmabuf issue.

Thankfully, this being correct isn't a requirement for me, but it's
something which _should_ be fixed.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 16:49                                 ` Russell King - ARM Linux
@ 2013-06-12 17:05                                   ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 17:05 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 12, 2013 at 05:49:14PM +0100, Russell King - ARM Linux wrote:
> On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
> > On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
> > <linux@arm.linux.org.uk> wrote:
> > > And having thought about this driver, DRM some more, I'm now of the
> > > opinion that DRM is not suitable for driving hardware where the GPU is
> > > an entirely separate IP block from the display side.
> > >
> > > DRM is modelled after the PC setup where your "graphics card" does
> > > everything - it has the GPU, display and connectors all integrated
> > > together.  This is not the case on embedded SoCs, which can be a
> > > collection of different IPs all integrated together.
> > 
> > actually it isn't even the case on desktop/laptop anymore, where you
> > can have one gpu with scanout and a second one without (or just with
> > display controller not hooked up to anything, etc, etc)
> > 
> > That is the point of dmabuf and the upcoming fence/reservation stuff.
> 
> Okay, but dmabuf really needs to be fixed, because as it stands this API
> is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
> to have its return value ignored - mappings can fail, and (b) the returned
> number indicates how many entries are valid for the _mapped_ version of
> the scatterlist.
> 
> Both these points are important if your DMA API implementation uses an
> IOMMU, which may coalesce the scatterlist array when creating mappings -
> much as described in Documentation/DMA-API.txt and
> Documentation/DMA-API-HOWTO.txt.
> 
> That is not all DRMs fault - (a) is attributable to DRM's prime
> implementation:
> 
>         sgt = obj->dev->driver->gem_prime_get_sg_table(obj);
> 
>         if (!IS_ERR_OR_NULL(sgt))
>                 dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);
> 
> and quite why it does the dma_map_sg() beneath the struct_mutex is
> beyond me - if the array of pages isn't safe without the mutex being
> held, then it isn't safe after the dma_map_sg() operation has completed
> and the mutex has been released.
> 
> However, (b) is more a problem for dmabuf (which I just rather aptly
> mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
> to return the value from dma_map_sg(), or it needs to stop requiring
> this of the .map_dma_buf method and have it done by the caller of
> this method so the caller can have access to that returned value.
> 
> Added Sumit Semwal to this email for the dmabuf issue.
> 
> Thankfully, this being correct isn't a requirement for me, but it's
> something which _should_ be fixed.

Okay, so Sumit Semwal has been a victim of TI's cuts, so don't expect
dma_buf to get sorted by the original author...

Now we come to this:

drivers/gpu/drm/exynos/exynos_drm_dmabuf.c: return dma_buf_export(exynos_gem_obj, &exynos_dmabuf_ops,
drivers/gpu/drm/exynos/exynos_drm_dmabuf.c-                         exynos_gem_obj->base.size, flags);
drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c:  return dma_buf_export(obj, &omap_dmabuf_ops, obj->size, flags);
drivers/gpu/drm/drm_prime.c:        return dma_buf_export(obj, &drm_gem_prime_dmabuf_ops, obj->size,
drivers/gpu/drm/drm_prime.c-                              0600);
drivers/gpu/drm/i915/i915_gem_dmabuf.c:     return dma_buf_export(obj, &i915_dmabuf_ops, obj->base.size, flags);

Of the three implementations which don't use the generic version, they all
pass 'flags' to dma_buf_export.  drm_prime.c doesn't, it passes a fixed
file mode.  What's the correct version, or is flags | 0600 actually the
right one (as flags only contains O_CLOEXEC)?

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 17:05                                   ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 17:05 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, Dave Airlie, Jason Cooper, linux-arm-kernel,
	Sebastian Hesselbarth

On Wed, Jun 12, 2013 at 05:49:14PM +0100, Russell King - ARM Linux wrote:
> On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
> > On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
> > <linux@arm.linux.org.uk> wrote:
> > > And having thought about this driver, DRM some more, I'm now of the
> > > opinion that DRM is not suitable for driving hardware where the GPU is
> > > an entirely separate IP block from the display side.
> > >
> > > DRM is modelled after the PC setup where your "graphics card" does
> > > everything - it has the GPU, display and connectors all integrated
> > > together.  This is not the case on embedded SoCs, which can be a
> > > collection of different IPs all integrated together.
> > 
> > actually it isn't even the case on desktop/laptop anymore, where you
> > can have one gpu with scanout and a second one without (or just with
> > display controller not hooked up to anything, etc, etc)
> > 
> > That is the point of dmabuf and the upcoming fence/reservation stuff.
> 
> Okay, but dmabuf really needs to be fixed, because as it stands this API
> is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
> to have its return value ignored - mappings can fail, and (b) the returned
> number indicates how many entries are valid for the _mapped_ version of
> the scatterlist.
> 
> Both these points are important if your DMA API implementation uses an
> IOMMU, which may coalesce the scatterlist array when creating mappings -
> much as described in Documentation/DMA-API.txt and
> Documentation/DMA-API-HOWTO.txt.
> 
> That is not all DRMs fault - (a) is attributable to DRM's prime
> implementation:
> 
>         sgt = obj->dev->driver->gem_prime_get_sg_table(obj);
> 
>         if (!IS_ERR_OR_NULL(sgt))
>                 dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);
> 
> and quite why it does the dma_map_sg() beneath the struct_mutex is
> beyond me - if the array of pages isn't safe without the mutex being
> held, then it isn't safe after the dma_map_sg() operation has completed
> and the mutex has been released.
> 
> However, (b) is more a problem for dmabuf (which I just rather aptly
> mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
> to return the value from dma_map_sg(), or it needs to stop requiring
> this of the .map_dma_buf method and have it done by the caller of
> this method so the caller can have access to that returned value.
> 
> Added Sumit Semwal to this email for the dmabuf issue.
> 
> Thankfully, this being correct isn't a requirement for me, but it's
> something which _should_ be fixed.

Okay, so Sumit Semwal has been a victim of TI's cuts, so don't expect
dma_buf to get sorted by the original author...

Now we come to this:

drivers/gpu/drm/exynos/exynos_drm_dmabuf.c: return dma_buf_export(exynos_gem_obj, &exynos_dmabuf_ops,
drivers/gpu/drm/exynos/exynos_drm_dmabuf.c-                         exynos_gem_obj->base.size, flags);
drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c:  return dma_buf_export(obj, &omap_dmabuf_ops, obj->size, flags);
drivers/gpu/drm/drm_prime.c:        return dma_buf_export(obj, &drm_gem_prime_dmabuf_ops, obj->size,
drivers/gpu/drm/drm_prime.c-                              0600);
drivers/gpu/drm/i915/i915_gem_dmabuf.c:     return dma_buf_export(obj, &i915_dmabuf_ops, obj->base.size, flags);

Of the three implementations which don't use the generic version, they all
pass 'flags' to dma_buf_export.  drm_prime.c doesn't, it passes a fixed
file mode.  What's the correct version, or is flags | 0600 actually the
right one (as flags only contains O_CLOEXEC)?

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 17:05                                   ` Russell King - ARM Linux
@ 2013-06-12 19:40                                     ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 19:40 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 12, 2013 at 06:05:12PM +0100, Russell King - ARM Linux wrote:
> On Wed, Jun 12, 2013 at 05:49:14PM +0100, Russell King - ARM Linux wrote:
> > On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
> > > On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
> > > <linux@arm.linux.org.uk> wrote:
> > > > And having thought about this driver, DRM some more, I'm now of the
> > > > opinion that DRM is not suitable for driving hardware where the GPU is
> > > > an entirely separate IP block from the display side.
> > > >
> > > > DRM is modelled after the PC setup where your "graphics card" does
> > > > everything - it has the GPU, display and connectors all integrated
> > > > together.  This is not the case on embedded SoCs, which can be a
> > > > collection of different IPs all integrated together.
> > > 
> > > actually it isn't even the case on desktop/laptop anymore, where you
> > > can have one gpu with scanout and a second one without (or just with
> > > display controller not hooked up to anything, etc, etc)
> > > 
> > > That is the point of dmabuf and the upcoming fence/reservation stuff.
> > 
> > Okay, but dmabuf really needs to be fixed, because as it stands this API
> > is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
> > to have its return value ignored - mappings can fail, and (b) the returned
> > number indicates how many entries are valid for the _mapped_ version of
> > the scatterlist.
> > 
> > Both these points are important if your DMA API implementation uses an
> > IOMMU, which may coalesce the scatterlist array when creating mappings -
> > much as described in Documentation/DMA-API.txt and
> > Documentation/DMA-API-HOWTO.txt.
> > 
> > That is not all DRMs fault - (a) is attributable to DRM's prime
> > implementation:
> > 
> >         sgt = obj->dev->driver->gem_prime_get_sg_table(obj);
> > 
> >         if (!IS_ERR_OR_NULL(sgt))
> >                 dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);
> > 
> > and quite why it does the dma_map_sg() beneath the struct_mutex is
> > beyond me - if the array of pages isn't safe without the mutex being
> > held, then it isn't safe after the dma_map_sg() operation has completed
> > and the mutex has been released.
> > 
> > However, (b) is more a problem for dmabuf (which I just rather aptly
> > mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
> > to return the value from dma_map_sg(), or it needs to stop requiring
> > this of the .map_dma_buf method and have it done by the caller of
> > this method so the caller can have access to that returned value.
> > 
> > Added Sumit Semwal to this email for the dmabuf issue.
> > 
> > Thankfully, this being correct isn't a requirement for me, but it's
> > something which _should_ be fixed.
> 
> Okay, so Sumit Semwal has been a victim of TI's cuts, so don't expect
> dma_buf to get sorted by the original author...

And now the next muckup in dma-buf/drm_prime - though it's arguably the
fault of IS_ERR_OR_NULL existing in the first place:

drm_gem_prime_import()
{
        sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
        if (IS_ERR_OR_NULL(sgt)) {
                ret = PTR_ERR(sgt);
                goto fail_detach;
        }
fail_detach:
        dma_buf_detach(dma_buf, attach);
        return ERR_PTR(ret);
}

What happens if sgt is NULL here?  What value does ret get?  What does
drm_gem_prime_import() return?  How is that interpreted by
drm_gem_prime_fd_to_handle() ?

I can probably oops a kernel running DRM through this if I can manipulate
a dma_buf_map_attachment() to return NULL (there probably is a driver
which is capable of doing so.)

APIs which use IS_ERR_OR_NULL() are problems waiting to happen; we've
already reached some concensus a while back to get rid of this helper,
if only it wasn't soo prolific (like its errors are.)

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 19:40                                     ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 19:40 UTC (permalink / raw)
  To: Rob Clark
  Cc: linux-arm-kernel, Dave Airlie, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Wed, Jun 12, 2013 at 06:05:12PM +0100, Russell King - ARM Linux wrote:
> On Wed, Jun 12, 2013 at 05:49:14PM +0100, Russell King - ARM Linux wrote:
> > On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
> > > On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
> > > <linux@arm.linux.org.uk> wrote:
> > > > And having thought about this driver, DRM some more, I'm now of the
> > > > opinion that DRM is not suitable for driving hardware where the GPU is
> > > > an entirely separate IP block from the display side.
> > > >
> > > > DRM is modelled after the PC setup where your "graphics card" does
> > > > everything - it has the GPU, display and connectors all integrated
> > > > together.  This is not the case on embedded SoCs, which can be a
> > > > collection of different IPs all integrated together.
> > > 
> > > actually it isn't even the case on desktop/laptop anymore, where you
> > > can have one gpu with scanout and a second one without (or just with
> > > display controller not hooked up to anything, etc, etc)
> > > 
> > > That is the point of dmabuf and the upcoming fence/reservation stuff.
> > 
> > Okay, but dmabuf really needs to be fixed, because as it stands this API
> > is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
> > to have its return value ignored - mappings can fail, and (b) the returned
> > number indicates how many entries are valid for the _mapped_ version of
> > the scatterlist.
> > 
> > Both these points are important if your DMA API implementation uses an
> > IOMMU, which may coalesce the scatterlist array when creating mappings -
> > much as described in Documentation/DMA-API.txt and
> > Documentation/DMA-API-HOWTO.txt.
> > 
> > That is not all DRMs fault - (a) is attributable to DRM's prime
> > implementation:
> > 
> >         sgt = obj->dev->driver->gem_prime_get_sg_table(obj);
> > 
> >         if (!IS_ERR_OR_NULL(sgt))
> >                 dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);
> > 
> > and quite why it does the dma_map_sg() beneath the struct_mutex is
> > beyond me - if the array of pages isn't safe without the mutex being
> > held, then it isn't safe after the dma_map_sg() operation has completed
> > and the mutex has been released.
> > 
> > However, (b) is more a problem for dmabuf (which I just rather aptly
> > mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
> > to return the value from dma_map_sg(), or it needs to stop requiring
> > this of the .map_dma_buf method and have it done by the caller of
> > this method so the caller can have access to that returned value.
> > 
> > Added Sumit Semwal to this email for the dmabuf issue.
> > 
> > Thankfully, this being correct isn't a requirement for me, but it's
> > something which _should_ be fixed.
> 
> Okay, so Sumit Semwal has been a victim of TI's cuts, so don't expect
> dma_buf to get sorted by the original author...

And now the next muckup in dma-buf/drm_prime - though it's arguably the
fault of IS_ERR_OR_NULL existing in the first place:

drm_gem_prime_import()
{
        sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
        if (IS_ERR_OR_NULL(sgt)) {
                ret = PTR_ERR(sgt);
                goto fail_detach;
        }
fail_detach:
        dma_buf_detach(dma_buf, attach);
        return ERR_PTR(ret);
}

What happens if sgt is NULL here?  What value does ret get?  What does
drm_gem_prime_import() return?  How is that interpreted by
drm_gem_prime_fd_to_handle() ?

I can probably oops a kernel running DRM through this if I can manipulate
a dma_buf_map_attachment() to return NULL (there probably is a driver
which is capable of doing so.)

APIs which use IS_ERR_OR_NULL() are problems waiting to happen; we've
already reached some concensus a while back to get rid of this helper,
if only it wasn't soo prolific (like its errors are.)

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 17:05                                   ` Russell King - ARM Linux
@ 2013-06-12 20:04                                     ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-12 20:04 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 12, 2013 at 1:05 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Wed, Jun 12, 2013 at 05:49:14PM +0100, Russell King - ARM Linux wrote:
>> On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
>> > On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
>> > <linux@arm.linux.org.uk> wrote:
>> > > And having thought about this driver, DRM some more, I'm now of the
>> > > opinion that DRM is not suitable for driving hardware where the GPU is
>> > > an entirely separate IP block from the display side.
>> > >
>> > > DRM is modelled after the PC setup where your "graphics card" does
>> > > everything - it has the GPU, display and connectors all integrated
>> > > together.  This is not the case on embedded SoCs, which can be a
>> > > collection of different IPs all integrated together.
>> >
>> > actually it isn't even the case on desktop/laptop anymore, where you
>> > can have one gpu with scanout and a second one without (or just with
>> > display controller not hooked up to anything, etc, etc)
>> >
>> > That is the point of dmabuf and the upcoming fence/reservation stuff.
>>
>> Okay, but dmabuf really needs to be fixed, because as it stands this API
>> is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
>> to have its return value ignored - mappings can fail, and (b) the returned
>> number indicates how many entries are valid for the _mapped_ version of
>> the scatterlist.
>>
>> Both these points are important if your DMA API implementation uses an
>> IOMMU, which may coalesce the scatterlist array when creating mappings -
>> much as described in Documentation/DMA-API.txt and
>> Documentation/DMA-API-HOWTO.txt.
>>
>> That is not all DRMs fault - (a) is attributable to DRM's prime
>> implementation:
>>
>>         sgt = obj->dev->driver->gem_prime_get_sg_table(obj);
>>
>>         if (!IS_ERR_OR_NULL(sgt))
>>                 dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);
>>
>> and quite why it does the dma_map_sg() beneath the struct_mutex is
>> beyond me - if the array of pages isn't safe without the mutex being
>> held, then it isn't safe after the dma_map_sg() operation has completed
>> and the mutex has been released.
>>
>> However, (b) is more a problem for dmabuf (which I just rather aptly
>> mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
>> to return the value from dma_map_sg(), or it needs to stop requiring
>> this of the .map_dma_buf method and have it done by the caller of
>> this method so the caller can have access to that returned value.
>>
>> Added Sumit Semwal to this email for the dmabuf issue.
>>
>> Thankfully, this being correct isn't a requirement for me, but it's
>> something which _should_ be fixed.

(just some quick answers.. I'll read rest of thread a bit later when I
have some time)

> Okay, so Sumit Semwal has been a victim of TI's cuts, so don't expect
> dma_buf to get sorted by the original author...

Sumit is at linaro, and should still be caring for dma_buf (although
w/ different email addr)

> Now we come to this:
>
> drivers/gpu/drm/exynos/exynos_drm_dmabuf.c: return dma_buf_export(exynos_gem_obj, &exynos_dmabuf_ops,
> drivers/gpu/drm/exynos/exynos_drm_dmabuf.c-                         exynos_gem_obj->base.size, flags);
> drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c:  return dma_buf_export(obj, &omap_dmabuf_ops, obj->size, flags);
> drivers/gpu/drm/drm_prime.c:        return dma_buf_export(obj, &drm_gem_prime_dmabuf_ops, obj->size,
> drivers/gpu/drm/drm_prime.c-                              0600);
> drivers/gpu/drm/i915/i915_gem_dmabuf.c:     return dma_buf_export(obj, &i915_dmabuf_ops, obj->base.size, flags);

the drm_prime.c version was a more recent addition of "dmabuf
helpers".. not all the drivers use 'em (which seems to be a good
thing)

> Of the three implementations which don't use the generic version, they all
> pass 'flags' to dma_buf_export.  drm_prime.c doesn't, it passes a fixed
> file mode.  What's the correct version, or is flags | 0600 actually the
> right one (as flags only contains O_CLOEXEC)?

the drm_prime version is wrong.. the O_CLOEXEC flag should have been
passed along from what userspace requested when exporting the dmabuf


BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 20:04                                     ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-12 20:04 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jason Cooper, dri-devel, linux-arm-kernel, Sebastian Hesselbarth

On Wed, Jun 12, 2013 at 1:05 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Wed, Jun 12, 2013 at 05:49:14PM +0100, Russell King - ARM Linux wrote:
>> On Wed, Jun 12, 2013 at 09:56:22AM -0400, Rob Clark wrote:
>> > On Wed, Jun 12, 2013 at 9:48 AM, Russell King - ARM Linux
>> > <linux@arm.linux.org.uk> wrote:
>> > > And having thought about this driver, DRM some more, I'm now of the
>> > > opinion that DRM is not suitable for driving hardware where the GPU is
>> > > an entirely separate IP block from the display side.
>> > >
>> > > DRM is modelled after the PC setup where your "graphics card" does
>> > > everything - it has the GPU, display and connectors all integrated
>> > > together.  This is not the case on embedded SoCs, which can be a
>> > > collection of different IPs all integrated together.
>> >
>> > actually it isn't even the case on desktop/laptop anymore, where you
>> > can have one gpu with scanout and a second one without (or just with
>> > display controller not hooked up to anything, etc, etc)
>> >
>> > That is the point of dmabuf and the upcoming fence/reservation stuff.
>>
>> Okay, but dmabuf really needs to be fixed, because as it stands this API
>> is really quite broken wrt the DMA API.  dma_map_sg() is (a) not supposed
>> to have its return value ignored - mappings can fail, and (b) the returned
>> number indicates how many entries are valid for the _mapped_ version of
>> the scatterlist.
>>
>> Both these points are important if your DMA API implementation uses an
>> IOMMU, which may coalesce the scatterlist array when creating mappings -
>> much as described in Documentation/DMA-API.txt and
>> Documentation/DMA-API-HOWTO.txt.
>>
>> That is not all DRMs fault - (a) is attributable to DRM's prime
>> implementation:
>>
>>         sgt = obj->dev->driver->gem_prime_get_sg_table(obj);
>>
>>         if (!IS_ERR_OR_NULL(sgt))
>>                 dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir);
>>
>> and quite why it does the dma_map_sg() beneath the struct_mutex is
>> beyond me - if the array of pages isn't safe without the mutex being
>> held, then it isn't safe after the dma_map_sg() operation has completed
>> and the mutex has been released.
>>
>> However, (b) is more a problem for dmabuf (which I just rather aptly
>> mistyped as dmabug) because either dmabuf's .map_dma_buf method needs
>> to return the value from dma_map_sg(), or it needs to stop requiring
>> this of the .map_dma_buf method and have it done by the caller of
>> this method so the caller can have access to that returned value.
>>
>> Added Sumit Semwal to this email for the dmabuf issue.
>>
>> Thankfully, this being correct isn't a requirement for me, but it's
>> something which _should_ be fixed.

(just some quick answers.. I'll read rest of thread a bit later when I
have some time)

> Okay, so Sumit Semwal has been a victim of TI's cuts, so don't expect
> dma_buf to get sorted by the original author...

Sumit is at linaro, and should still be caring for dma_buf (although
w/ different email addr)

> Now we come to this:
>
> drivers/gpu/drm/exynos/exynos_drm_dmabuf.c: return dma_buf_export(exynos_gem_obj, &exynos_dmabuf_ops,
> drivers/gpu/drm/exynos/exynos_drm_dmabuf.c-                         exynos_gem_obj->base.size, flags);
> drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c:  return dma_buf_export(obj, &omap_dmabuf_ops, obj->size, flags);
> drivers/gpu/drm/drm_prime.c:        return dma_buf_export(obj, &drm_gem_prime_dmabuf_ops, obj->size,
> drivers/gpu/drm/drm_prime.c-                              0600);
> drivers/gpu/drm/i915/i915_gem_dmabuf.c:     return dma_buf_export(obj, &i915_dmabuf_ops, obj->base.size, flags);

the drm_prime.c version was a more recent addition of "dmabuf
helpers".. not all the drivers use 'em (which seems to be a good
thing)

> Of the three implementations which don't use the generic version, they all
> pass 'flags' to dma_buf_export.  drm_prime.c doesn't, it passes a fixed
> file mode.  What's the correct version, or is flags | 0600 actually the
> right one (as flags only contains O_CLOEXEC)?

the drm_prime version is wrong.. the O_CLOEXEC flag should have been
passed along from what userspace requested when exporting the dmabuf


BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 19:40                                     ` Russell King - ARM Linux
@ 2013-06-12 23:00                                       ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 23:00 UTC (permalink / raw)
  To: linux-arm-kernel

And here's another one - look carefully at this path:

                buf = dev->driver->gem_prime_export(dev, obj, flags);
                if (IS_ERR(buf)) {
                        /* normally the created dma-buf takes ownership of the ref,
                         * but if that fails then drop the ref
                         */
                        drm_gem_object_unreference_unlocked(obj);
                        mutex_unlock(&file_priv->prime.lock);
                        return PTR_ERR(buf);
                }
                obj->export_dma_buf = buf;
                *prime_fd = dma_buf_fd(buf, flags);
        }
        /* if we've exported this buffer the cheat and add it to the import list
         * so we get the correct handle back
         */
        ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
                        obj->export_dma_buf, handle);
        if (ret) {
                drm_gem_object_unreference_unlocked(obj);
                mutex_unlock(&file_priv->prime.lock);
                return ret;
        }

So in the event of drm_prime_add_imported_buf_handle() returning an error,
we return that error to our caller (which will eventually be userspace)
saying that we failed.

However, we've already setup the export and obtained an fd for it, which
we resultingly now leak in that situation.

Now, second problem here - consider what happens if you ask for the same
object to be exported a second (or more) times.  Note that
obj->export_dma_buf will now be set, so we take a different path through
this code:

        if (obj->export_dma_buf) {
                get_dma_buf(obj->export_dma_buf);
                *prime_fd = dma_buf_fd(obj->export_dma_buf, flags);
                drm_gem_object_unreference_unlocked(obj);
	} else {
	}
        /* if we've exported this buffer the cheat and add it to the import list
         * so we get the correct handle back
         */
        ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
                        obj->export_dma_buf, handle);
        if (ret) {
                drm_gem_object_unreference_unlocked(obj);
                mutex_unlock(&file_priv->prime.lock);
                return ret;
        }

Now, if I in a loop in userspace doing this:

	do {
		drmPrimeHandleToFD(..., &fd);
		close(fd);
	} while (1);

How long do you think it might take to exhaust the kmalloc() inside
drm_prime_add_imported_buf_handle() ?

It's not even trivially fixable, because drm_gem_dmabuf_release() doesn't
call drm_prime_remove_imported_buf_handle() because it has no knowledge
of the drm_prime_file_private structure to search for these things...

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-12 23:00                                       ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-12 23:00 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, Dave Airlie, Jason Cooper, linux-arm-kernel,
	Sebastian Hesselbarth

And here's another one - look carefully at this path:

                buf = dev->driver->gem_prime_export(dev, obj, flags);
                if (IS_ERR(buf)) {
                        /* normally the created dma-buf takes ownership of the ref,
                         * but if that fails then drop the ref
                         */
                        drm_gem_object_unreference_unlocked(obj);
                        mutex_unlock(&file_priv->prime.lock);
                        return PTR_ERR(buf);
                }
                obj->export_dma_buf = buf;
                *prime_fd = dma_buf_fd(buf, flags);
        }
        /* if we've exported this buffer the cheat and add it to the import list
         * so we get the correct handle back
         */
        ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
                        obj->export_dma_buf, handle);
        if (ret) {
                drm_gem_object_unreference_unlocked(obj);
                mutex_unlock(&file_priv->prime.lock);
                return ret;
        }

So in the event of drm_prime_add_imported_buf_handle() returning an error,
we return that error to our caller (which will eventually be userspace)
saying that we failed.

However, we've already setup the export and obtained an fd for it, which
we resultingly now leak in that situation.

Now, second problem here - consider what happens if you ask for the same
object to be exported a second (or more) times.  Note that
obj->export_dma_buf will now be set, so we take a different path through
this code:

        if (obj->export_dma_buf) {
                get_dma_buf(obj->export_dma_buf);
                *prime_fd = dma_buf_fd(obj->export_dma_buf, flags);
                drm_gem_object_unreference_unlocked(obj);
	} else {
	}
        /* if we've exported this buffer the cheat and add it to the import list
         * so we get the correct handle back
         */
        ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
                        obj->export_dma_buf, handle);
        if (ret) {
                drm_gem_object_unreference_unlocked(obj);
                mutex_unlock(&file_priv->prime.lock);
                return ret;
        }

Now, if I in a loop in userspace doing this:

	do {
		drmPrimeHandleToFD(..., &fd);
		close(fd);
	} while (1);

How long do you think it might take to exhaust the kmalloc() inside
drm_prime_add_imported_buf_handle() ?

It's not even trivially fixable, because drm_gem_dmabuf_release() doesn't
call drm_prime_remove_imported_buf_handle() because it has no knowledge
of the drm_prime_file_private structure to search for these things...

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 23:00                                       ` Russell King - ARM Linux
@ 2013-06-13  0:17                                         ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-13  0:17 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 12, 2013 at 7:00 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> And here's another one - look carefully at this path:
>
>                 buf = dev->driver->gem_prime_export(dev, obj, flags);
>                 if (IS_ERR(buf)) {
>                         /* normally the created dma-buf takes ownership of the ref,
>                          * but if that fails then drop the ref
>                          */
>                         drm_gem_object_unreference_unlocked(obj);
>                         mutex_unlock(&file_priv->prime.lock);
>                         return PTR_ERR(buf);
>                 }
>                 obj->export_dma_buf = buf;
>                 *prime_fd = dma_buf_fd(buf, flags);
>         }
>         /* if we've exported this buffer the cheat and add it to the import list
>          * so we get the correct handle back
>          */
>         ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
>                         obj->export_dma_buf, handle);
>         if (ret) {
>                 drm_gem_object_unreference_unlocked(obj);
>                 mutex_unlock(&file_priv->prime.lock);
>                 return ret;
>         }
>
> So in the event of drm_prime_add_imported_buf_handle() returning an error,
> we return that error to our caller (which will eventually be userspace)
> saying that we failed.
>
> However, we've already setup the export and obtained an fd for it, which
> we resultingly now leak in that situation.

well, it is a semi-leak, I believe.  The gem object won't leak, but we
do leak an fd.  We don't end up with an extra reference to the gem bo,
so when it is eventually destroyed, the dmabuf/file will be cleaned
up.  I'm not quite sure off the top of my head what ends up happening
if you have an open fd and destroy the flie.  That might not be
terribly good.

I do think it would be better to immediately clean up and destroy the
dmabuf and fd.

> Now, second problem here - consider what happens if you ask for the same
> object to be exported a second (or more) times.  Note that
> obj->export_dma_buf will now be set, so we take a different path through
> this code:
>
>         if (obj->export_dma_buf) {
>                 get_dma_buf(obj->export_dma_buf);
>                 *prime_fd = dma_buf_fd(obj->export_dma_buf, flags);
>                 drm_gem_object_unreference_unlocked(obj);
>         } else {
>         }
>         /* if we've exported this buffer the cheat and add it to the import list
>          * so we get the correct handle back
>          */
>         ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
>                         obj->export_dma_buf, handle);
>         if (ret) {
>                 drm_gem_object_unreference_unlocked(obj);
>                 mutex_unlock(&file_priv->prime.lock);
>                 return ret;
>         }
>
> Now, if I in a loop in userspace doing this:
>
>         do {
>                 drmPrimeHandleToFD(..., &fd);
>                 close(fd);
>         } while (1);
>
> How long do you think it might take to exhaust the kmalloc() inside
> drm_prime_add_imported_buf_handle() ?
>
> It's not even trivially fixable, because drm_gem_dmabuf_release() doesn't
> call drm_prime_remove_imported_buf_handle() because it has no knowledge
> of the drm_prime_file_private structure to search for these things...

Do you mind having a look at the latest?  This has changed recently so
we don't re-add to the import list, so I don't believe your userspace
loop would cause any issue.

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-13  0:17                                         ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-13  0:17 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: dri-devel, Jason Cooper, linux-arm-kernel, Sebastian Hesselbarth

On Wed, Jun 12, 2013 at 7:00 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> And here's another one - look carefully at this path:
>
>                 buf = dev->driver->gem_prime_export(dev, obj, flags);
>                 if (IS_ERR(buf)) {
>                         /* normally the created dma-buf takes ownership of the ref,
>                          * but if that fails then drop the ref
>                          */
>                         drm_gem_object_unreference_unlocked(obj);
>                         mutex_unlock(&file_priv->prime.lock);
>                         return PTR_ERR(buf);
>                 }
>                 obj->export_dma_buf = buf;
>                 *prime_fd = dma_buf_fd(buf, flags);
>         }
>         /* if we've exported this buffer the cheat and add it to the import list
>          * so we get the correct handle back
>          */
>         ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
>                         obj->export_dma_buf, handle);
>         if (ret) {
>                 drm_gem_object_unreference_unlocked(obj);
>                 mutex_unlock(&file_priv->prime.lock);
>                 return ret;
>         }
>
> So in the event of drm_prime_add_imported_buf_handle() returning an error,
> we return that error to our caller (which will eventually be userspace)
> saying that we failed.
>
> However, we've already setup the export and obtained an fd for it, which
> we resultingly now leak in that situation.

well, it is a semi-leak, I believe.  The gem object won't leak, but we
do leak an fd.  We don't end up with an extra reference to the gem bo,
so when it is eventually destroyed, the dmabuf/file will be cleaned
up.  I'm not quite sure off the top of my head what ends up happening
if you have an open fd and destroy the flie.  That might not be
terribly good.

I do think it would be better to immediately clean up and destroy the
dmabuf and fd.

> Now, second problem here - consider what happens if you ask for the same
> object to be exported a second (or more) times.  Note that
> obj->export_dma_buf will now be set, so we take a different path through
> this code:
>
>         if (obj->export_dma_buf) {
>                 get_dma_buf(obj->export_dma_buf);
>                 *prime_fd = dma_buf_fd(obj->export_dma_buf, flags);
>                 drm_gem_object_unreference_unlocked(obj);
>         } else {
>         }
>         /* if we've exported this buffer the cheat and add it to the import list
>          * so we get the correct handle back
>          */
>         ret = drm_prime_add_imported_buf_handle(&file_priv->prime,
>                         obj->export_dma_buf, handle);
>         if (ret) {
>                 drm_gem_object_unreference_unlocked(obj);
>                 mutex_unlock(&file_priv->prime.lock);
>                 return ret;
>         }
>
> Now, if I in a loop in userspace doing this:
>
>         do {
>                 drmPrimeHandleToFD(..., &fd);
>                 close(fd);
>         } while (1);
>
> How long do you think it might take to exhaust the kmalloc() inside
> drm_prime_add_imported_buf_handle() ?
>
> It's not even trivially fixable, because drm_gem_dmabuf_release() doesn't
> call drm_prime_remove_imported_buf_handle() because it has no knowledge
> of the drm_prime_file_private structure to search for these things...

Do you mind having a look at the latest?  This has changed recently so
we don't re-add to the import list, so I don't believe your userspace
loop would cause any issue.

BR,
-R

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-12 23:00                                       ` Russell King - ARM Linux
@ 2013-06-13 11:19                                         ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-13 11:19 UTC (permalink / raw)
  To: linux-arm-kernel

And another issue...

What is drm_crtc_helper_set_mode() passed as the fb argument?  Is it
the old fb, or the new fb?

bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
                              struct drm_display_mode *mode,
                              int x, int y,
                              struct drm_framebuffer *old_fb)
...
int drm_crtc_helper_set_config(struct drm_mode_set *set)
{
...
        save_set.fb = set->crtc->fb;
...
                        old_fb = set->crtc->fb;
                        set->crtc->fb = set->fb;
                        if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
                                                      set->x, set->y,
                                                      old_fb)) {
...
        /* Try to restore the config */
        if (mode_changed &&
            !drm_crtc_helper_set_mode(save_set.crtc, save_set.mode, save_set.x,
                                      save_set.y, save_set.fb))
}
...
int drm_helper_resume_force_mode(struct drm_device *dev)
{
...
                ret = drm_crtc_helper_set_mode(crtc, &crtc->mode,
                                               crtc->x, crtc->y, crtc->fb);

The function prototype implies it's the old fb, as does the first use.
The second and third uses of it clearly show it being the fb which we
wish to be displayed.

The deeper I look, the more bugs there seem to be in this DRM stuff,
and I'm continuing to look because I'm chasing a framebuffer refcount
bug.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-13 11:19                                         ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-13 11:19 UTC (permalink / raw)
  To: Rob Clark
  Cc: linux-arm-kernel, Dave Airlie, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

And another issue...

What is drm_crtc_helper_set_mode() passed as the fb argument?  Is it
the old fb, or the new fb?

bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
                              struct drm_display_mode *mode,
                              int x, int y,
                              struct drm_framebuffer *old_fb)
...
int drm_crtc_helper_set_config(struct drm_mode_set *set)
{
...
        save_set.fb = set->crtc->fb;
...
                        old_fb = set->crtc->fb;
                        set->crtc->fb = set->fb;
                        if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
                                                      set->x, set->y,
                                                      old_fb)) {
...
        /* Try to restore the config */
        if (mode_changed &&
            !drm_crtc_helper_set_mode(save_set.crtc, save_set.mode, save_set.x,
                                      save_set.y, save_set.fb))
}
...
int drm_helper_resume_force_mode(struct drm_device *dev)
{
...
                ret = drm_crtc_helper_set_mode(crtc, &crtc->mode,
                                               crtc->x, crtc->y, crtc->fb);

The function prototype implies it's the old fb, as does the first use.
The second and third uses of it clearly show it being the fb which we
wish to be displayed.

The deeper I look, the more bugs there seem to be in this DRM stuff,
and I'm continuing to look because I'm chasing a framebuffer refcount
bug.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-13 11:19                                         ` Russell King - ARM Linux
@ 2013-06-13 11:50                                           ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-13 11:50 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> The deeper I look, the more bugs there seem to be in this DRM stuff,
> and I'm continuing to look because I'm chasing a framebuffer refcount
> bug.

So, this refcount bug - I think I've just found it.  This is the flow of
references to the new fb on mode set:

drm_mode_setcrtc():
                        fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
        set.fb = fb;
        ret = drm_mode_set_config_internal(&set);
drm_mode_set_config_internal():
        fb = set->fb;
        ret = crtc->funcs->set_config(set);
drm_crtc_helper_set_config():
                        old_fb = set->crtc->fb;
                        set->crtc->fb = set->fb;
                        if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
                                                      set->x, set->y,
                                                      old_fb)) {
                drm_helper_disable_unused_functions(dev);
drm_helper_disable_unused_functions():
        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
                crtc->enabled = drm_helper_crtc_in_use(crtc);
                if (!crtc->enabled) {
                        crtc->fb = NULL;
		}
	}
back to drm_mode_set_config_internal():
        if (ret == 0) {
                if (fb)
                        drm_framebuffer_reference(fb);
back to drm_mode_setcrtc():
        if (fb)
                drm_framebuffer_unreference(fb);

Assuming success all the way through, what happens when a CRTC is unused
is:

1. We obtain a reference in drm_mode_setcrtc() via the lookup.
2. We set the mode
3. In trying to set the mode, we discover that all connectors for the CRTC
   are in the disconnected state, and so we disable the CRTC
4. We set crtc->fb to NULL
5. back in drm_mode_set_config_internal(), we take a reference on the
   framebuffer irrespective of this.
6. back in drm_mode_setcrtc(), we drop the original reference caused by
   the lookup.

We now have a framebuffer with a reference count incremented by one but
no actual reference to it - the CRTC's reference is completely lost by
the action of drm_helper_disable_unused_functions().

You could argue that it's something the driver should deal with - fine,
but what if it only implements the DPMS method?  Should it drop a
reference to the framebuffer when DPMS instructs it to turn off?  Surely
not, because that means when DPMS turns stuff back on you're missing a
refcount.

Are drivers required to implement a disable function and cater for the
imbalance in the upper layers of code?  If so, this is not a clean
design.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-13 11:50                                           ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-13 11:50 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, Dave Airlie, Jason Cooper, linux-arm-kernel,
	Sebastian Hesselbarth

On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> The deeper I look, the more bugs there seem to be in this DRM stuff,
> and I'm continuing to look because I'm chasing a framebuffer refcount
> bug.

So, this refcount bug - I think I've just found it.  This is the flow of
references to the new fb on mode set:

drm_mode_setcrtc():
                        fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
        set.fb = fb;
        ret = drm_mode_set_config_internal(&set);
drm_mode_set_config_internal():
        fb = set->fb;
        ret = crtc->funcs->set_config(set);
drm_crtc_helper_set_config():
                        old_fb = set->crtc->fb;
                        set->crtc->fb = set->fb;
                        if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
                                                      set->x, set->y,
                                                      old_fb)) {
                drm_helper_disable_unused_functions(dev);
drm_helper_disable_unused_functions():
        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
                crtc->enabled = drm_helper_crtc_in_use(crtc);
                if (!crtc->enabled) {
                        crtc->fb = NULL;
		}
	}
back to drm_mode_set_config_internal():
        if (ret == 0) {
                if (fb)
                        drm_framebuffer_reference(fb);
back to drm_mode_setcrtc():
        if (fb)
                drm_framebuffer_unreference(fb);

Assuming success all the way through, what happens when a CRTC is unused
is:

1. We obtain a reference in drm_mode_setcrtc() via the lookup.
2. We set the mode
3. In trying to set the mode, we discover that all connectors for the CRTC
   are in the disconnected state, and so we disable the CRTC
4. We set crtc->fb to NULL
5. back in drm_mode_set_config_internal(), we take a reference on the
   framebuffer irrespective of this.
6. back in drm_mode_setcrtc(), we drop the original reference caused by
   the lookup.

We now have a framebuffer with a reference count incremented by one but
no actual reference to it - the CRTC's reference is completely lost by
the action of drm_helper_disable_unused_functions().

You could argue that it's something the driver should deal with - fine,
but what if it only implements the DPMS method?  Should it drop a
reference to the framebuffer when DPMS instructs it to turn off?  Surely
not, because that means when DPMS turns stuff back on you're missing a
refcount.

Are drivers required to implement a disable function and cater for the
imbalance in the upper layers of code?  If so, this is not a clean
design.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-13 11:19                                         ` Russell King - ARM Linux
@ 2013-06-13 12:52                                           ` Rob Clark
  -1 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-13 12:52 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 7:19 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> And another issue...
>
> What is drm_crtc_helper_set_mode() passed as the fb argument?  Is it
> the old fb, or the new fb?
>
> bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
>                               struct drm_display_mode *mode,
>                               int x, int y,
>                               struct drm_framebuffer *old_fb)
> ...
> int drm_crtc_helper_set_config(struct drm_mode_set *set)
> {
> ...
>         save_set.fb = set->crtc->fb;
> ...
>                         old_fb = set->crtc->fb;
>                         set->crtc->fb = set->fb;
>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>                                                       set->x, set->y,
>                                                       old_fb)) {
> ...
>         /* Try to restore the config */
>         if (mode_changed &&
>             !drm_crtc_helper_set_mode(save_set.crtc, save_set.mode, save_set.x,
>                                       save_set.y, save_set.fb))
> }
> ...
> int drm_helper_resume_force_mode(struct drm_device *dev)
> {
> ...
>                 ret = drm_crtc_helper_set_mode(crtc, &crtc->mode,
>                                                crtc->x, crtc->y, crtc->fb);
>
> The function prototype implies it's the old fb, as does the first use.
> The second and third uses of it clearly show it being the fb which we
> wish to be displayed.

most of the embedded drivers should ignore the old_fb.. the API is a
bit odd but the purpose is to help drivers that need to pin/unpin the
gem objects backing the fb.  The ones that do, do something like:

  foo_pin(new_fb);
  foo_unpin(old_fb);

if the two are the same, it works out.

Note that even for the non error paths, new_fb and old_fb could be the same.

Possibly something worth clarifying in the docs.

BR,
-R

> The deeper I look, the more bugs there seem to be in this DRM stuff,
> and I'm continuing to look because I'm chasing a framebuffer refcount
> bug.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-13 12:52                                           ` Rob Clark
  0 siblings, 0 replies; 120+ messages in thread
From: Rob Clark @ 2013-06-13 12:52 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: linux-arm-kernel, Dave Airlie, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Thu, Jun 13, 2013 at 7:19 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> And another issue...
>
> What is drm_crtc_helper_set_mode() passed as the fb argument?  Is it
> the old fb, or the new fb?
>
> bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
>                               struct drm_display_mode *mode,
>                               int x, int y,
>                               struct drm_framebuffer *old_fb)
> ...
> int drm_crtc_helper_set_config(struct drm_mode_set *set)
> {
> ...
>         save_set.fb = set->crtc->fb;
> ...
>                         old_fb = set->crtc->fb;
>                         set->crtc->fb = set->fb;
>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>                                                       set->x, set->y,
>                                                       old_fb)) {
> ...
>         /* Try to restore the config */
>         if (mode_changed &&
>             !drm_crtc_helper_set_mode(save_set.crtc, save_set.mode, save_set.x,
>                                       save_set.y, save_set.fb))
> }
> ...
> int drm_helper_resume_force_mode(struct drm_device *dev)
> {
> ...
>                 ret = drm_crtc_helper_set_mode(crtc, &crtc->mode,
>                                                crtc->x, crtc->y, crtc->fb);
>
> The function prototype implies it's the old fb, as does the first use.
> The second and third uses of it clearly show it being the fb which we
> wish to be displayed.

most of the embedded drivers should ignore the old_fb.. the API is a
bit odd but the purpose is to help drivers that need to pin/unpin the
gem objects backing the fb.  The ones that do, do something like:

  foo_pin(new_fb);
  foo_unpin(old_fb);

if the two are the same, it works out.

Note that even for the non error paths, new_fb and old_fb could be the same.

Possibly something worth clarifying in the docs.

BR,
-R

> The deeper I look, the more bugs there seem to be in this DRM stuff,
> and I'm continuing to look because I'm chasing a framebuffer refcount
> bug.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-13 12:52                                           ` Rob Clark
@ 2013-06-13 12:58                                             ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-13 12:58 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 2:52 PM, Rob Clark <robdclark@gmail.com> wrote:
> most of the embedded drivers should ignore the old_fb.. the API is a
> bit odd but the purpose is to help drivers that need to pin/unpin the
> gem objects backing the fb.  The ones that do, do something like:
>
>   foo_pin(new_fb);
>   foo_unpin(old_fb);
>
> if the two are the same, it works out.
>
> Note that even for the non error paths, new_fb and old_fb could be the same.
>
> Possibly something worth clarifying in the docs.

One implication this has is that the crtc helper assumes that the
driver modeset callbacks only fail before the driver internally has
done the pin/unpin dance. If the new fb is already the one which
should be unpinned and the modeset fails after that point, then we'll
leak stuff.

So drivers need to ensure that they undo any pin/unpins (and other fb
refcounted resource handling) if they're ->modeset callback fails.

In the new shiny drm/i915 modeset helper code we've flipped around
those semantics, but imo for the crtc helper it does fit into the
larger assumptions of the crtc helper code:
- crtc helper code assumes that all ->disable callbacks are stateless
- hence it can put the _new_ requested state into the sw tracking
structures before the modeset starts so that ->mode_fixup callbacks
could e.g. check the pixel format of the new fb.

Flipping that around would remove one of the cornerstones of the crtc
helpers, so imo doesn't make much sense. But as i915.ko demonstrates
with a bit of effort it's no problem to have a completely different
modeset sequence driver infrastructure.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-13 12:58                                             ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-13 12:58 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, Russell King - ARM Linux, Jason Cooper,
	linux-arm-kernel, Sebastian Hesselbarth

On Thu, Jun 13, 2013 at 2:52 PM, Rob Clark <robdclark@gmail.com> wrote:
> most of the embedded drivers should ignore the old_fb.. the API is a
> bit odd but the purpose is to help drivers that need to pin/unpin the
> gem objects backing the fb.  The ones that do, do something like:
>
>   foo_pin(new_fb);
>   foo_unpin(old_fb);
>
> if the two are the same, it works out.
>
> Note that even for the non error paths, new_fb and old_fb could be the same.
>
> Possibly something worth clarifying in the docs.

One implication this has is that the crtc helper assumes that the
driver modeset callbacks only fail before the driver internally has
done the pin/unpin dance. If the new fb is already the one which
should be unpinned and the modeset fails after that point, then we'll
leak stuff.

So drivers need to ensure that they undo any pin/unpins (and other fb
refcounted resource handling) if they're ->modeset callback fails.

In the new shiny drm/i915 modeset helper code we've flipped around
those semantics, but imo for the crtc helper it does fit into the
larger assumptions of the crtc helper code:
- crtc helper code assumes that all ->disable callbacks are stateless
- hence it can put the _new_ requested state into the sw tracking
structures before the modeset starts so that ->mode_fixup callbacks
could e.g. check the pixel format of the new fb.

Flipping that around would remove one of the cornerstones of the crtc
helpers, so imo doesn't make much sense. But as i915.ko demonstrates
with a bit of effort it's no problem to have a completely different
modeset sequence driver infrastructure.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-13 11:50                                           ` Russell King - ARM Linux
@ 2013-06-13 13:03                                             ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-13 13:03 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
> On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> > The deeper I look, the more bugs there seem to be in this DRM stuff,
> > and I'm continuing to look because I'm chasing a framebuffer refcount
> > bug.
> 
> So, this refcount bug - I think I've just found it.  This is the flow of
> references to the new fb on mode set:
> 
> drm_mode_setcrtc():
>                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>         set.fb = fb;
>         ret = drm_mode_set_config_internal(&set);
> drm_mode_set_config_internal():
>         fb = set->fb;
>         ret = crtc->funcs->set_config(set);
> drm_crtc_helper_set_config():
>                         old_fb = set->crtc->fb;
>                         set->crtc->fb = set->fb;
>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>                                                       set->x, set->y,
>                                                       old_fb)) {
>                 drm_helper_disable_unused_functions(dev);
> drm_helper_disable_unused_functions():
>         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>                 crtc->enabled = drm_helper_crtc_in_use(crtc);
>                 if (!crtc->enabled) {
>                         crtc->fb = NULL;
> 		}
> 	}
> back to drm_mode_set_config_internal():
>         if (ret == 0) {
>                 if (fb)
>                         drm_framebuffer_reference(fb);
> back to drm_mode_setcrtc():
>         if (fb)
>                 drm_framebuffer_unreference(fb);
> 
> Assuming success all the way through, what happens when a CRTC is unused
> is:
> 
> 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
> 2. We set the mode
> 3. In trying to set the mode, we discover that all connectors for the CRTC
>    are in the disconnected state, and so we disable the CRTC
> 4. We set crtc->fb to NULL
> 5. back in drm_mode_set_config_internal(), we take a reference on the
>    framebuffer irrespective of this.
> 6. back in drm_mode_setcrtc(), we drop the original reference caused by
>    the lookup.
> 
> We now have a framebuffer with a reference count incremented by one but
> no actual reference to it - the CRTC's reference is completely lost by
> the action of drm_helper_disable_unused_functions().
> 
> You could argue that it's something the driver should deal with - fine,
> but what if it only implements the DPMS method?  Should it drop a
> reference to the framebuffer when DPMS instructs it to turn off?  Surely
> not, because that means when DPMS turns stuff back on you're missing a
> refcount.
> 
> Are drivers required to implement a disable function and cater for the
> imbalance in the upper layers of code?  If so, this is not a clean
> design.

There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
to be called with set->fb set but set->mode NULL, then we overwrite
set->fb to NULL.  Again, that results in a lost reference.

For the time being, I'm using this patch, which solves my dropped
refcount problem, and marks the other possible dropped reference.
Either that check needs to be removed or it needs to properly drop
the refcount on the fb before 'losing' the reference to it.

diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index dd64a06..774d7a6 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -2014,13 +2014,16 @@ int drm_mode_set_config_internal(struct drm_mode_set *set)
 
 	old_fb = crtc->fb;
 	fb = set->fb;
+	if (fb)
+		drm_framebuffer_reference(fb);
 
 	ret = crtc->funcs->set_config(set);
 	if (ret == 0) {
 		if (old_fb)
 			drm_framebuffer_unreference(old_fb);
+	} else {
 		if (fb)
-			drm_framebuffer_reference(fb);
+			drm_framebuffer_unreference(fb);
 	}
 
 	return ret;
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
index 7b2d378..0d18fb2 100644
--- a/drivers/gpu/drm/drm_crtc_helper.c
+++ b/drivers/gpu/drm/drm_crtc_helper.c
@@ -299,6 +299,8 @@ void drm_helper_disable_unused_functions(struct drm_device *dev)
 				(*crtc_funcs->disable)(crtc);
 			else
 				(*crtc_funcs->dpms)(crtc, DRM_MODE_DPMS_OFF);
+			if (crtc->fb)
+				drm_framebuffer_unreference(crtc->fb);
 			crtc->fb = NULL;
 		}
 	}
@@ -573,6 +575,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
 
 	crtc_funcs = set->crtc->helper_private;
 
+	/* FIXME: this loses a refcount on the fb */
 	if (!set->mode)
 		set->fb = NULL;
 

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-13 13:03                                             ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-13 13:03 UTC (permalink / raw)
  To: Rob Clark
  Cc: linux-arm-kernel, Dave Airlie, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
> On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> > The deeper I look, the more bugs there seem to be in this DRM stuff,
> > and I'm continuing to look because I'm chasing a framebuffer refcount
> > bug.
> 
> So, this refcount bug - I think I've just found it.  This is the flow of
> references to the new fb on mode set:
> 
> drm_mode_setcrtc():
>                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>         set.fb = fb;
>         ret = drm_mode_set_config_internal(&set);
> drm_mode_set_config_internal():
>         fb = set->fb;
>         ret = crtc->funcs->set_config(set);
> drm_crtc_helper_set_config():
>                         old_fb = set->crtc->fb;
>                         set->crtc->fb = set->fb;
>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>                                                       set->x, set->y,
>                                                       old_fb)) {
>                 drm_helper_disable_unused_functions(dev);
> drm_helper_disable_unused_functions():
>         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>                 crtc->enabled = drm_helper_crtc_in_use(crtc);
>                 if (!crtc->enabled) {
>                         crtc->fb = NULL;
> 		}
> 	}
> back to drm_mode_set_config_internal():
>         if (ret == 0) {
>                 if (fb)
>                         drm_framebuffer_reference(fb);
> back to drm_mode_setcrtc():
>         if (fb)
>                 drm_framebuffer_unreference(fb);
> 
> Assuming success all the way through, what happens when a CRTC is unused
> is:
> 
> 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
> 2. We set the mode
> 3. In trying to set the mode, we discover that all connectors for the CRTC
>    are in the disconnected state, and so we disable the CRTC
> 4. We set crtc->fb to NULL
> 5. back in drm_mode_set_config_internal(), we take a reference on the
>    framebuffer irrespective of this.
> 6. back in drm_mode_setcrtc(), we drop the original reference caused by
>    the lookup.
> 
> We now have a framebuffer with a reference count incremented by one but
> no actual reference to it - the CRTC's reference is completely lost by
> the action of drm_helper_disable_unused_functions().
> 
> You could argue that it's something the driver should deal with - fine,
> but what if it only implements the DPMS method?  Should it drop a
> reference to the framebuffer when DPMS instructs it to turn off?  Surely
> not, because that means when DPMS turns stuff back on you're missing a
> refcount.
> 
> Are drivers required to implement a disable function and cater for the
> imbalance in the upper layers of code?  If so, this is not a clean
> design.

There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
to be called with set->fb set but set->mode NULL, then we overwrite
set->fb to NULL.  Again, that results in a lost reference.

For the time being, I'm using this patch, which solves my dropped
refcount problem, and marks the other possible dropped reference.
Either that check needs to be removed or it needs to properly drop
the refcount on the fb before 'losing' the reference to it.

diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index dd64a06..774d7a6 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -2014,13 +2014,16 @@ int drm_mode_set_config_internal(struct drm_mode_set *set)
 
 	old_fb = crtc->fb;
 	fb = set->fb;
+	if (fb)
+		drm_framebuffer_reference(fb);
 
 	ret = crtc->funcs->set_config(set);
 	if (ret == 0) {
 		if (old_fb)
 			drm_framebuffer_unreference(old_fb);
+	} else {
 		if (fb)
-			drm_framebuffer_reference(fb);
+			drm_framebuffer_unreference(fb);
 	}
 
 	return ret;
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
index 7b2d378..0d18fb2 100644
--- a/drivers/gpu/drm/drm_crtc_helper.c
+++ b/drivers/gpu/drm/drm_crtc_helper.c
@@ -299,6 +299,8 @@ void drm_helper_disable_unused_functions(struct drm_device *dev)
 				(*crtc_funcs->disable)(crtc);
 			else
 				(*crtc_funcs->dpms)(crtc, DRM_MODE_DPMS_OFF);
+			if (crtc->fb)
+				drm_framebuffer_unreference(crtc->fb);
 			crtc->fb = NULL;
 		}
 	}
@@ -573,6 +575,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
 
 	crtc_funcs = set->crtc->helper_private;
 
+	/* FIXME: this loses a refcount on the fb */
 	if (!set->mode)
 		set->fb = NULL;

^ permalink raw reply related	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-13 11:50                                           ` Russell King - ARM Linux
@ 2013-06-14 13:53                                             ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 13:53 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
> On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> > The deeper I look, the more bugs there seem to be in this DRM stuff,
> > and I'm continuing to look because I'm chasing a framebuffer refcount
> > bug.
> 
> So, this refcount bug - I think I've just found it.  This is the flow of
> references to the new fb on mode set:
> 
> drm_mode_setcrtc():
>                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>         set.fb = fb;
>         ret = drm_mode_set_config_internal(&set);
> drm_mode_set_config_internal():
>         fb = set->fb;
>         ret = crtc->funcs->set_config(set);
> drm_crtc_helper_set_config():
>                         old_fb = set->crtc->fb;
>                         set->crtc->fb = set->fb;
>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>                                                       set->x, set->y,
>                                                       old_fb)) {
>                 drm_helper_disable_unused_functions(dev);
> drm_helper_disable_unused_functions():
>         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>                 crtc->enabled = drm_helper_crtc_in_use(crtc);
>                 if (!crtc->enabled) {
>                         crtc->fb = NULL;
> 		}
> 	}
> back to drm_mode_set_config_internal():
>         if (ret == 0) {
>                 if (fb)
>                         drm_framebuffer_reference(fb);
> back to drm_mode_setcrtc():
>         if (fb)
>                 drm_framebuffer_unreference(fb);
> 
> Assuming success all the way through, what happens when a CRTC is unused
> is:
> 
> 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
> 2. We set the mode
> 3. In trying to set the mode, we discover that all connectors for the CRTC
>    are in the disconnected state, and so we disable the CRTC
> 4. We set crtc->fb to NULL
> 5. back in drm_mode_set_config_internal(), we take a reference on the
>    framebuffer irrespective of this.
> 6. back in drm_mode_setcrtc(), we drop the original reference caused by
>    the lookup.
> 
> We now have a framebuffer with a reference count incremented by one but
> no actual reference to it - the CRTC's reference is completely lost by
> the action of drm_helper_disable_unused_functions().
> 
> You could argue that it's something the driver should deal with - fine,
> but what if it only implements the DPMS method?  Should it drop a
> reference to the framebuffer when DPMS instructs it to turn off?  Surely
> not, because that means when DPMS turns stuff back on you're missing a
> refcount.
> 
> Are drivers required to implement a disable function and cater for the
> imbalance in the upper layers of code?  If so, this is not a clean
> design.

Yep, if your driver grabs additional references (underlying gem object,
pinning, whatever) you need to wire up your own ->disable hook to drop
those. Note that for truly dumb kms drivers which only ever allocate an
fb, the upper layer actually _does_ take care of all the refcounting.

Also note the crtc helpers in drm_crtc_helper.c are purely optional. The
real drm core -> driver interface is all contained in drm_crtc.c. And crtc
helpers do make a few critical design assumptions about how your hw works
(and there's a bit room for api cleanup, I agree on that). So if they
simply don't work out for you no one will get upset if you roll your own
modeset infrastructure. And in drm/i915 we've had to do just that since
the impedance mismatch between crtc helper assumptions and what our hw
needed grew to big (and in really fundamental ways, not just a bit of
interface ugliness like you're seeing here).
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 13:53                                             ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 13:53 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: linux-arm-kernel, Rob Clark, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
> On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> > The deeper I look, the more bugs there seem to be in this DRM stuff,
> > and I'm continuing to look because I'm chasing a framebuffer refcount
> > bug.
> 
> So, this refcount bug - I think I've just found it.  This is the flow of
> references to the new fb on mode set:
> 
> drm_mode_setcrtc():
>                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>         set.fb = fb;
>         ret = drm_mode_set_config_internal(&set);
> drm_mode_set_config_internal():
>         fb = set->fb;
>         ret = crtc->funcs->set_config(set);
> drm_crtc_helper_set_config():
>                         old_fb = set->crtc->fb;
>                         set->crtc->fb = set->fb;
>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>                                                       set->x, set->y,
>                                                       old_fb)) {
>                 drm_helper_disable_unused_functions(dev);
> drm_helper_disable_unused_functions():
>         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>                 crtc->enabled = drm_helper_crtc_in_use(crtc);
>                 if (!crtc->enabled) {
>                         crtc->fb = NULL;
> 		}
> 	}
> back to drm_mode_set_config_internal():
>         if (ret == 0) {
>                 if (fb)
>                         drm_framebuffer_reference(fb);
> back to drm_mode_setcrtc():
>         if (fb)
>                 drm_framebuffer_unreference(fb);
> 
> Assuming success all the way through, what happens when a CRTC is unused
> is:
> 
> 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
> 2. We set the mode
> 3. In trying to set the mode, we discover that all connectors for the CRTC
>    are in the disconnected state, and so we disable the CRTC
> 4. We set crtc->fb to NULL
> 5. back in drm_mode_set_config_internal(), we take a reference on the
>    framebuffer irrespective of this.
> 6. back in drm_mode_setcrtc(), we drop the original reference caused by
>    the lookup.
> 
> We now have a framebuffer with a reference count incremented by one but
> no actual reference to it - the CRTC's reference is completely lost by
> the action of drm_helper_disable_unused_functions().
> 
> You could argue that it's something the driver should deal with - fine,
> but what if it only implements the DPMS method?  Should it drop a
> reference to the framebuffer when DPMS instructs it to turn off?  Surely
> not, because that means when DPMS turns stuff back on you're missing a
> refcount.
> 
> Are drivers required to implement a disable function and cater for the
> imbalance in the upper layers of code?  If so, this is not a clean
> design.

Yep, if your driver grabs additional references (underlying gem object,
pinning, whatever) you need to wire up your own ->disable hook to drop
those. Note that for truly dumb kms drivers which only ever allocate an
fb, the upper layer actually _does_ take care of all the refcounting.

Also note the crtc helpers in drm_crtc_helper.c are purely optional. The
real drm core -> driver interface is all contained in drm_crtc.c. And crtc
helpers do make a few critical design assumptions about how your hw works
(and there's a bit room for api cleanup, I agree on that). So if they
simply don't work out for you no one will get upset if you roll your own
modeset infrastructure. And in drm/i915 we've had to do just that since
the impedance mismatch between crtc helper assumptions and what our hw
needed grew to big (and in really fundamental ways, not just a bit of
interface ugliness like you're seeing here).
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-13 13:03                                             ` Russell King - ARM Linux
@ 2013-06-14 14:23                                               ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 14:23 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 3:03 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
>> On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
>> > The deeper I look, the more bugs there seem to be in this DRM stuff,
>> > and I'm continuing to look because I'm chasing a framebuffer refcount
>> > bug.
>>
>> So, this refcount bug - I think I've just found it.  This is the flow of
>> references to the new fb on mode set:
>>
>> drm_mode_setcrtc():
>>                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>>         set.fb = fb;
>>         ret = drm_mode_set_config_internal(&set);
>> drm_mode_set_config_internal():
>>         fb = set->fb;
>>         ret = crtc->funcs->set_config(set);
>> drm_crtc_helper_set_config():
>>                         old_fb = set->crtc->fb;
>>                         set->crtc->fb = set->fb;
>>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>>                                                       set->x, set->y,
>>                                                       old_fb)) {
>>                 drm_helper_disable_unused_functions(dev);
>> drm_helper_disable_unused_functions():
>>         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>>                 crtc->enabled = drm_helper_crtc_in_use(crtc);
>>                 if (!crtc->enabled) {
>>                         crtc->fb = NULL;
>>               }
>>       }
>> back to drm_mode_set_config_internal():
>>         if (ret == 0) {
>>                 if (fb)
>>                         drm_framebuffer_reference(fb);
>> back to drm_mode_setcrtc():
>>         if (fb)
>>                 drm_framebuffer_unreference(fb);
>>
>> Assuming success all the way through, what happens when a CRTC is unused
>> is:
>>
>> 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
>> 2. We set the mode
>> 3. In trying to set the mode, we discover that all connectors for the CRTC
>>    are in the disconnected state, and so we disable the CRTC
>> 4. We set crtc->fb to NULL
>> 5. back in drm_mode_set_config_internal(), we take a reference on the
>>    framebuffer irrespective of this.
>> 6. back in drm_mode_setcrtc(), we drop the original reference caused by
>>    the lookup.
>>
>> We now have a framebuffer with a reference count incremented by one but
>> no actual reference to it - the CRTC's reference is completely lost by
>> the action of drm_helper_disable_unused_functions().
>>
>> You could argue that it's something the driver should deal with - fine,
>> but what if it only implements the DPMS method?  Should it drop a
>> reference to the framebuffer when DPMS instructs it to turn off?  Surely
>> not, because that means when DPMS turns stuff back on you're missing a
>> refcount.
>>
>> Are drivers required to implement a disable function and cater for the
>> imbalance in the upper layers of code?  If so, this is not a clean
>> design.
>
> There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
> to be called with set->fb set but set->mode NULL, then we overwrite
> set->fb to NULL.  Again, that results in a lost reference.
>
> For the time being, I'm using this patch, which solves my dropped
> refcount problem, and marks the other possible dropped reference.
> Either that check needs to be removed or it needs to properly drop
> the refcount on the fb before 'losing' the reference to it.

Scrap my other mail, I see now where the leaking happens. One of them
is interface abuse which is now fixed (and i915 has a bunch of BUG_ONs
to enforce them). The other one is indeed a real case that eluded me
when I've done the refcountification for drm_framebuffers. I'll hack
up some patches, since this seems to be a good excuse to port some of
the i915 modeset improvements back to the crtc helpers.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 14:23                                               ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 14:23 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: dri-devel, Jason Cooper, linux-arm-kernel, Sebastian Hesselbarth

On Thu, Jun 13, 2013 at 3:03 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
>> On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
>> > The deeper I look, the more bugs there seem to be in this DRM stuff,
>> > and I'm continuing to look because I'm chasing a framebuffer refcount
>> > bug.
>>
>> So, this refcount bug - I think I've just found it.  This is the flow of
>> references to the new fb on mode set:
>>
>> drm_mode_setcrtc():
>>                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>>         set.fb = fb;
>>         ret = drm_mode_set_config_internal(&set);
>> drm_mode_set_config_internal():
>>         fb = set->fb;
>>         ret = crtc->funcs->set_config(set);
>> drm_crtc_helper_set_config():
>>                         old_fb = set->crtc->fb;
>>                         set->crtc->fb = set->fb;
>>                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
>>                                                       set->x, set->y,
>>                                                       old_fb)) {
>>                 drm_helper_disable_unused_functions(dev);
>> drm_helper_disable_unused_functions():
>>         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>>                 crtc->enabled = drm_helper_crtc_in_use(crtc);
>>                 if (!crtc->enabled) {
>>                         crtc->fb = NULL;
>>               }
>>       }
>> back to drm_mode_set_config_internal():
>>         if (ret == 0) {
>>                 if (fb)
>>                         drm_framebuffer_reference(fb);
>> back to drm_mode_setcrtc():
>>         if (fb)
>>                 drm_framebuffer_unreference(fb);
>>
>> Assuming success all the way through, what happens when a CRTC is unused
>> is:
>>
>> 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
>> 2. We set the mode
>> 3. In trying to set the mode, we discover that all connectors for the CRTC
>>    are in the disconnected state, and so we disable the CRTC
>> 4. We set crtc->fb to NULL
>> 5. back in drm_mode_set_config_internal(), we take a reference on the
>>    framebuffer irrespective of this.
>> 6. back in drm_mode_setcrtc(), we drop the original reference caused by
>>    the lookup.
>>
>> We now have a framebuffer with a reference count incremented by one but
>> no actual reference to it - the CRTC's reference is completely lost by
>> the action of drm_helper_disable_unused_functions().
>>
>> You could argue that it's something the driver should deal with - fine,
>> but what if it only implements the DPMS method?  Should it drop a
>> reference to the framebuffer when DPMS instructs it to turn off?  Surely
>> not, because that means when DPMS turns stuff back on you're missing a
>> refcount.
>>
>> Are drivers required to implement a disable function and cater for the
>> imbalance in the upper layers of code?  If so, this is not a clean
>> design.
>
> There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
> to be called with set->fb set but set->mode NULL, then we overwrite
> set->fb to NULL.  Again, that results in a lost reference.
>
> For the time being, I'm using this patch, which solves my dropped
> refcount problem, and marks the other possible dropped reference.
> Either that check needs to be removed or it needs to properly drop
> the refcount on the fb before 'losing' the reference to it.

Scrap my other mail, I see now where the leaking happens. One of them
is interface abuse which is now fixed (and i915 has a bunch of BUG_ONs
to enforce them). The other one is indeed a real case that eluded me
when I've done the refcountification for drm_framebuffers. I'll hack
up some patches, since this seems to be a good excuse to port some of
the i915 modeset improvements back to the crtc helpers.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-14 13:53                                             ` Daniel Vetter
@ 2013-06-14 14:27                                               ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-14 14:27 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 14, 2013 at 03:53:41PM +0200, Daniel Vetter wrote:
> On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
> > On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> > > The deeper I look, the more bugs there seem to be in this DRM stuff,
> > > and I'm continuing to look because I'm chasing a framebuffer refcount
> > > bug.
> > 
> > So, this refcount bug - I think I've just found it.  This is the flow of
> > references to the new fb on mode set:
> > 
> > drm_mode_setcrtc():
> >                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
> >         set.fb = fb;
> >         ret = drm_mode_set_config_internal(&set);
> > drm_mode_set_config_internal():
> >         fb = set->fb;
> >         ret = crtc->funcs->set_config(set);
> > drm_crtc_helper_set_config():
> >                         old_fb = set->crtc->fb;
> >                         set->crtc->fb = set->fb;
> >                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
> >                                                       set->x, set->y,
> >                                                       old_fb)) {
> >                 drm_helper_disable_unused_functions(dev);
> > drm_helper_disable_unused_functions():
> >         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> >                 crtc->enabled = drm_helper_crtc_in_use(crtc);
> >                 if (!crtc->enabled) {
> >                         crtc->fb = NULL;
> > 		}
> > 	}
> > back to drm_mode_set_config_internal():
> >         if (ret == 0) {
> >                 if (fb)
> >                         drm_framebuffer_reference(fb);
> > back to drm_mode_setcrtc():
> >         if (fb)
> >                 drm_framebuffer_unreference(fb);
> > 
> > Assuming success all the way through, what happens when a CRTC is unused
> > is:
> > 
> > 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
> > 2. We set the mode
> > 3. In trying to set the mode, we discover that all connectors for the CRTC
> >    are in the disconnected state, and so we disable the CRTC
> > 4. We set crtc->fb to NULL
> > 5. back in drm_mode_set_config_internal(), we take a reference on the
> >    framebuffer irrespective of this.
> > 6. back in drm_mode_setcrtc(), we drop the original reference caused by
> >    the lookup.
> > 
> > We now have a framebuffer with a reference count incremented by one but
> > no actual reference to it - the CRTC's reference is completely lost by
> > the action of drm_helper_disable_unused_functions().
> > 
> > You could argue that it's something the driver should deal with - fine,
> > but what if it only implements the DPMS method?  Should it drop a
> > reference to the framebuffer when DPMS instructs it to turn off?  Surely
> > not, because that means when DPMS turns stuff back on you're missing a
> > refcount.
> > 
> > Are drivers required to implement a disable function and cater for the
> > imbalance in the upper layers of code?  If so, this is not a clean
> > design.
> 
> Yep, if your driver grabs additional references (underlying gem object,
> pinning, whatever) you need to wire up your own ->disable hook to drop
> those. Note that for truly dumb kms drivers which only ever allocate an
> fb, the upper layer actually _does_ take care of all the refcounting.

Look again at what I said above - that is _not_ the case.  My above
analysis is totally without considering any refcounting that the
driver itself may or may not do.  If the driver does no refcounting on
the framebuffer object, then the leak is present.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 14:27                                               ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-14 14:27 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: linux-arm-kernel, Rob Clark, Jason Cooper, dri-devel,
	Sebastian Hesselbarth

On Fri, Jun 14, 2013 at 03:53:41PM +0200, Daniel Vetter wrote:
> On Thu, Jun 13, 2013 at 12:50:16PM +0100, Russell King - ARM Linux wrote:
> > On Thu, Jun 13, 2013 at 12:19:03PM +0100, Russell King - ARM Linux wrote:
> > > The deeper I look, the more bugs there seem to be in this DRM stuff,
> > > and I'm continuing to look because I'm chasing a framebuffer refcount
> > > bug.
> > 
> > So, this refcount bug - I think I've just found it.  This is the flow of
> > references to the new fb on mode set:
> > 
> > drm_mode_setcrtc():
> >                         fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
> >         set.fb = fb;
> >         ret = drm_mode_set_config_internal(&set);
> > drm_mode_set_config_internal():
> >         fb = set->fb;
> >         ret = crtc->funcs->set_config(set);
> > drm_crtc_helper_set_config():
> >                         old_fb = set->crtc->fb;
> >                         set->crtc->fb = set->fb;
> >                         if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
> >                                                       set->x, set->y,
> >                                                       old_fb)) {
> >                 drm_helper_disable_unused_functions(dev);
> > drm_helper_disable_unused_functions():
> >         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> >                 crtc->enabled = drm_helper_crtc_in_use(crtc);
> >                 if (!crtc->enabled) {
> >                         crtc->fb = NULL;
> > 		}
> > 	}
> > back to drm_mode_set_config_internal():
> >         if (ret == 0) {
> >                 if (fb)
> >                         drm_framebuffer_reference(fb);
> > back to drm_mode_setcrtc():
> >         if (fb)
> >                 drm_framebuffer_unreference(fb);
> > 
> > Assuming success all the way through, what happens when a CRTC is unused
> > is:
> > 
> > 1. We obtain a reference in drm_mode_setcrtc() via the lookup.
> > 2. We set the mode
> > 3. In trying to set the mode, we discover that all connectors for the CRTC
> >    are in the disconnected state, and so we disable the CRTC
> > 4. We set crtc->fb to NULL
> > 5. back in drm_mode_set_config_internal(), we take a reference on the
> >    framebuffer irrespective of this.
> > 6. back in drm_mode_setcrtc(), we drop the original reference caused by
> >    the lookup.
> > 
> > We now have a framebuffer with a reference count incremented by one but
> > no actual reference to it - the CRTC's reference is completely lost by
> > the action of drm_helper_disable_unused_functions().
> > 
> > You could argue that it's something the driver should deal with - fine,
> > but what if it only implements the DPMS method?  Should it drop a
> > reference to the framebuffer when DPMS instructs it to turn off?  Surely
> > not, because that means when DPMS turns stuff back on you're missing a
> > refcount.
> > 
> > Are drivers required to implement a disable function and cater for the
> > imbalance in the upper layers of code?  If so, this is not a clean
> > design.
> 
> Yep, if your driver grabs additional references (underlying gem object,
> pinning, whatever) you need to wire up your own ->disable hook to drop
> those. Note that for truly dumb kms drivers which only ever allocate an
> fb, the upper layer actually _does_ take care of all the refcounting.

Look again at what I said above - that is _not_ the case.  My above
analysis is totally without considering any refcounting that the
driver itself may or may not do.  If the driver does no refcounting on
the framebuffer object, then the leak is present.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-14 14:23                                               ` Daniel Vetter
@ 2013-06-14 14:42                                                 ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-14 14:42 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 14, 2013 at 04:23:22PM +0200, Daniel Vetter wrote:
> On Thu, Jun 13, 2013 at 3:03 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
> > to be called with set->fb set but set->mode NULL, then we overwrite
> > set->fb to NULL.  Again, that results in a lost reference.
> >
> > For the time being, I'm using this patch, which solves my dropped
> > refcount problem, and marks the other possible dropped reference.
> > Either that check needs to be removed or it needs to properly drop
> > the refcount on the fb before 'losing' the reference to it.
> 
> Scrap my other mail, I see now where the leaking happens. One of them
> is interface abuse which is now fixed (and i915 has a bunch of BUG_ONs
> to enforce them). The other one is indeed a real case that eluded me
> when I've done the refcountification for drm_framebuffers. I'll hack
> up some patches, since this seems to be a good excuse to port some of
> the i915 modeset improvements back to the crtc helpers.

If you're happy with the patch I supplied, that's probably the minimal fix
which should go to stable kernels (I'm using 3.9 here) - this also counts
as a "user visible bug".  It's something I've tripped over which causes
exhausts memory and can prevent the X server from starting up.

If you want me to package the patch up with a commit message and sign-off...

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 14:42                                                 ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-14 14:42 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: dri-devel, Rob Clark, Jason Cooper, linux-arm-kernel,
	Sebastian Hesselbarth

On Fri, Jun 14, 2013 at 04:23:22PM +0200, Daniel Vetter wrote:
> On Thu, Jun 13, 2013 at 3:03 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
> > to be called with set->fb set but set->mode NULL, then we overwrite
> > set->fb to NULL.  Again, that results in a lost reference.
> >
> > For the time being, I'm using this patch, which solves my dropped
> > refcount problem, and marks the other possible dropped reference.
> > Either that check needs to be removed or it needs to properly drop
> > the refcount on the fb before 'losing' the reference to it.
> 
> Scrap my other mail, I see now where the leaking happens. One of them
> is interface abuse which is now fixed (and i915 has a bunch of BUG_ONs
> to enforce them). The other one is indeed a real case that eluded me
> when I've done the refcountification for drm_framebuffers. I'll hack
> up some patches, since this seems to be a good excuse to port some of
> the i915 modeset improvements back to the crtc helpers.

If you're happy with the patch I supplied, that's probably the minimal fix
which should go to stable kernels (I'm using 3.9 here) - this also counts
as a "user visible bug".  It's something I've tripped over which causes
exhausts memory and can prevent the X server from starting up.

If you want me to package the patch up with a commit message and sign-off...

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-14 14:42                                                 ` Russell King - ARM Linux
@ 2013-06-14 19:50                                                   ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 19:50 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 14, 2013 at 4:42 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Fri, Jun 14, 2013 at 04:23:22PM +0200, Daniel Vetter wrote:
>> On Thu, Jun 13, 2013 at 3:03 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
>> > to be called with set->fb set but set->mode NULL, then we overwrite
>> > set->fb to NULL.  Again, that results in a lost reference.
>> >
>> > For the time being, I'm using this patch, which solves my dropped
>> > refcount problem, and marks the other possible dropped reference.
>> > Either that check needs to be removed or it needs to properly drop
>> > the refcount on the fb before 'losing' the reference to it.
>>
>> Scrap my other mail, I see now where the leaking happens. One of them
>> is interface abuse which is now fixed (and i915 has a bunch of BUG_ONs
>> to enforce them). The other one is indeed a real case that eluded me
>> when I've done the refcountification for drm_framebuffers. I'll hack
>> up some patches, since this seems to be a good excuse to port some of
>> the i915 modeset improvements back to the crtc helpers.
>
> If you're happy with the patch I supplied, that's probably the minimal fix
> which should go to stable kernels (I'm using 3.9 here) - this also counts
> as a "user visible bug".  It's something I've tripped over which causes
> exhausts memory and can prevent the X server from starting up.
>
> If you want me to package the patch up with a commit message and sign-off..

Your patch doesn't fix drm/i915 (since we don't use the crtc helpers
any more). And I don't think it's good to have the refcounting
partially in the drm core and partially in drivers. I've also thrown a
few more things on top just to port a few of the i915 cleanups to the
crtc helper. I'll submit my patches asap (need to test them a bit more
first).
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 19:50                                                   ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 19:50 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: dri-devel, Jason Cooper, linux-arm-kernel, Sebastian Hesselbarth

On Fri, Jun 14, 2013 at 4:42 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Fri, Jun 14, 2013 at 04:23:22PM +0200, Daniel Vetter wrote:
>> On Thu, Jun 13, 2013 at 3:03 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > There's a bigger issue here - if it's possible for drm_crtc_helper_set_config()
>> > to be called with set->fb set but set->mode NULL, then we overwrite
>> > set->fb to NULL.  Again, that results in a lost reference.
>> >
>> > For the time being, I'm using this patch, which solves my dropped
>> > refcount problem, and marks the other possible dropped reference.
>> > Either that check needs to be removed or it needs to properly drop
>> > the refcount on the fb before 'losing' the reference to it.
>>
>> Scrap my other mail, I see now where the leaking happens. One of them
>> is interface abuse which is now fixed (and i915 has a bunch of BUG_ONs
>> to enforce them). The other one is indeed a real case that eluded me
>> when I've done the refcountification for drm_framebuffers. I'll hack
>> up some patches, since this seems to be a good excuse to port some of
>> the i915 modeset improvements back to the crtc helpers.
>
> If you're happy with the patch I supplied, that's probably the minimal fix
> which should go to stable kernels (I'm using 3.9 here) - this also counts
> as a "user visible bug".  It's something I've tripped over which causes
> exhausts memory and can prevent the X server from starting up.
>
> If you want me to package the patch up with a commit message and sign-off..

Your patch doesn't fix drm/i915 (since we don't use the crtc helpers
any more). And I don't think it's good to have the refcounting
partially in the drm core and partially in drivers. I've also thrown a
few more things on top just to port a few of the i915 cleanups to the
crtc helper. I'll submit my patches asap (need to test them a bit more
first).
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-14 19:50                                                   ` Daniel Vetter
@ 2013-06-14 22:15                                                     ` Russell King - ARM Linux
  -1 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-14 22:15 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 14, 2013 at 09:50:22PM +0200, Daniel Vetter wrote:
> On Fri, Jun 14, 2013 at 4:42 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > If you're happy with the patch I supplied, that's probably the minimal fix
> > which should go to stable kernels (I'm using 3.9 here) - this also counts
> > as a "user visible bug".  It's something I've tripped over which causes
> > exhausts memory and can prevent the X server from starting up.
> >
> > If you want me to package the patch up with a commit message and sign-off..
> 
> Your patch doesn't fix drm/i915 (since we don't use the crtc helpers
> any more). And I don't think it's good to have the refcounting
> partially in the drm core and partially in drivers.

Let me check what you mean by that.  I hope you mean that the drm core,
drm helpers and drivers are individually responsible for dropping any
refcount that they obtain on any object.

In other words, if the drm core takes a refcount on object X, then the
DRM core must be the code base which drops that refcount.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 22:15                                                     ` Russell King - ARM Linux
  0 siblings, 0 replies; 120+ messages in thread
From: Russell King - ARM Linux @ 2013-06-14 22:15 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: dri-devel, Rob Clark, Jason Cooper, linux-arm-kernel,
	Sebastian Hesselbarth

On Fri, Jun 14, 2013 at 09:50:22PM +0200, Daniel Vetter wrote:
> On Fri, Jun 14, 2013 at 4:42 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > If you're happy with the patch I supplied, that's probably the minimal fix
> > which should go to stable kernels (I'm using 3.9 here) - this also counts
> > as a "user visible bug".  It's something I've tripped over which causes
> > exhausts memory and can prevent the X server from starting up.
> >
> > If you want me to package the patch up with a commit message and sign-off..
> 
> Your patch doesn't fix drm/i915 (since we don't use the crtc helpers
> any more). And I don't think it's good to have the refcounting
> partially in the drm core and partially in drivers.

Let me check what you mean by that.  I hope you mean that the drm core,
drm helpers and drivers are individually responsible for dropping any
refcount that they obtain on any object.

In other words, if the drm core takes a refcount on object X, then the
DRM core must be the code base which drops that refcount.

^ permalink raw reply	[flat|nested] 120+ messages in thread

* [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
  2013-06-14 22:15                                                     ` Russell King - ARM Linux
@ 2013-06-14 22:36                                                       ` Daniel Vetter
  -1 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 22:36 UTC (permalink / raw)
  To: linux-arm-kernel

On Sat, Jun 15, 2013 at 12:15 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Fri, Jun 14, 2013 at 09:50:22PM +0200, Daniel Vetter wrote:
>> On Fri, Jun 14, 2013 at 4:42 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > If you're happy with the patch I supplied, that's probably the minimal fix
>> > which should go to stable kernels (I'm using 3.9 here) - this also counts
>> > as a "user visible bug".  It's something I've tripped over which causes
>> > exhausts memory and can prevent the X server from starting up.
>> >
>> > If you want me to package the patch up with a commit message and sign-off..
>>
>> Your patch doesn't fix drm/i915 (since we don't use the crtc helpers
>> any more). And I don't think it's good to have the refcounting
>> partially in the drm core and partially in drivers.
>
> Let me check what you mean by that.  I hope you mean that the drm core,
> drm helpers and drivers are individually responsible for dropping any
> refcount that they obtain on any object.
>
> In other words, if the drm core takes a refcount on object X, then the
> DRM core must be the code base which drops that refcount.

Yeah, that's the idea. But since with the currently quirky drm core
->set_config callback the drivers need to drop the reference if any
other crtc loses all its connectors. My patch also drops the ref for
that fb in the drm core functions. The reason for that is that we want
to switch to a global ->set_config function anyway (usually called
atomic modeset) to push the configuration for all the crtcs into the
hw in one step (or fail it completely ofc). The current approach leads
to too much flickering and some funny hacks in drivers ...

Of course such gradual transitions over mutliple kernel release (we're
shuffling backend code in drm/i915 since about 3.7 for this) leaves a
few ugly intermediat states. But I prefer to move into the overall
right direction with a bit of ugliness than doing such big transitions
in one big swoop.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

* Re: [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver
@ 2013-06-14 22:36                                                       ` Daniel Vetter
  0 siblings, 0 replies; 120+ messages in thread
From: Daniel Vetter @ 2013-06-14 22:36 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: dri-devel, Jason Cooper, linux-arm-kernel, Sebastian Hesselbarth

On Sat, Jun 15, 2013 at 12:15 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Fri, Jun 14, 2013 at 09:50:22PM +0200, Daniel Vetter wrote:
>> On Fri, Jun 14, 2013 at 4:42 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>> > If you're happy with the patch I supplied, that's probably the minimal fix
>> > which should go to stable kernels (I'm using 3.9 here) - this also counts
>> > as a "user visible bug".  It's something I've tripped over which causes
>> > exhausts memory and can prevent the X server from starting up.
>> >
>> > If you want me to package the patch up with a commit message and sign-off..
>>
>> Your patch doesn't fix drm/i915 (since we don't use the crtc helpers
>> any more). And I don't think it's good to have the refcounting
>> partially in the drm core and partially in drivers.
>
> Let me check what you mean by that.  I hope you mean that the drm core,
> drm helpers and drivers are individually responsible for dropping any
> refcount that they obtain on any object.
>
> In other words, if the drm core takes a refcount on object X, then the
> DRM core must be the code base which drops that refcount.

Yeah, that's the idea. But since with the currently quirky drm core
->set_config callback the drivers need to drop the reference if any
other crtc loses all its connectors. My patch also drops the ref for
that fb in the drm core functions. The reason for that is that we want
to switch to a global ->set_config function anyway (usually called
atomic modeset) to push the configuration for all the crtcs into the
hw in one step (or fail it completely ofc). The current approach leads
to too much flickering and some funny hacks in drivers ...

Of course such gradual transitions over mutliple kernel release (we're
shuffling backend code in drm/i915 since about 3.7 for this) leaves a
few ugly intermediat states. But I prefer to move into the overall
right direction with a bit of ugliness than doing such big transitions
in one big swoop.
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 120+ messages in thread

end of thread, other threads:[~2013-06-14 22:36 UTC | newest]

Thread overview: 120+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-06-09 19:06 [RFC v2 0/8] rmk's Dove DRM/TDA19988 Cubox driver Russell King - ARM Linux
2013-06-09 19:06 ` Russell King - ARM Linux
2013-06-09 19:29 ` [PATCH RFC 2/8] DRM: Armada: Add Armada DRM driver Russell King
2013-06-09 19:32   ` Russell King
2013-06-10 11:10   ` Sebastian Hesselbarth
2013-06-10 11:10     ` Sebastian Hesselbarth
2013-06-10 21:48     ` Russell King - ARM Linux
2013-06-10 21:48       ` Russell King - ARM Linux
2013-06-10 21:56       ` Sebastian Hesselbarth
2013-06-10 21:56         ` Sebastian Hesselbarth
2013-06-10 15:57   ` Rob Clark
2013-06-10 15:57     ` Rob Clark
2013-06-10 17:06     ` Russell King - ARM Linux
2013-06-10 17:06       ` Russell King - ARM Linux
2013-06-10 19:59       ` Rob Clark
2013-06-10 19:59         ` Rob Clark
2013-06-10 20:08         ` Russell King - ARM Linux
2013-06-10 20:08           ` Russell King - ARM Linux
2013-06-10 21:01           ` Rob Clark
2013-06-10 21:01             ` Rob Clark
2013-06-10 21:15             ` Russell King - ARM Linux
2013-06-10 21:15               ` Russell King - ARM Linux
2013-06-10 22:49               ` Rob Clark
2013-06-10 22:49                 ` Rob Clark
2013-06-10 22:56                 ` Russell King - ARM Linux
2013-06-10 22:56                   ` Russell King - ARM Linux
2013-06-10 23:17                   ` Rob Clark
2013-06-10 23:17                     ` Rob Clark
2013-06-10 23:24                     ` Dave Airlie
2013-06-10 23:24                       ` Dave Airlie
2013-06-10 23:35                       ` Rob Clark
2013-06-10 23:35                         ` Rob Clark
2013-06-10 23:36                       ` Russell King - ARM Linux
2013-06-10 23:36                         ` Russell King - ARM Linux
2013-06-10 23:48                         ` Dave Airlie
2013-06-10 23:48                           ` Dave Airlie
2013-06-10 23:56                           ` Russell King - ARM Linux
2013-06-10 23:56                             ` Russell King - ARM Linux
2013-06-12 13:48                           ` Russell King - ARM Linux
2013-06-12 13:48                             ` Russell King - ARM Linux
2013-06-12 13:56                             ` Rob Clark
2013-06-12 13:56                               ` Rob Clark
2013-06-12 16:49                               ` Russell King - ARM Linux
2013-06-12 16:49                                 ` Russell King - ARM Linux
2013-06-12 17:05                                 ` Russell King - ARM Linux
2013-06-12 17:05                                   ` Russell King - ARM Linux
2013-06-12 19:40                                   ` Russell King - ARM Linux
2013-06-12 19:40                                     ` Russell King - ARM Linux
2013-06-12 23:00                                     ` Russell King - ARM Linux
2013-06-12 23:00                                       ` Russell King - ARM Linux
2013-06-13  0:17                                       ` Rob Clark
2013-06-13  0:17                                         ` Rob Clark
2013-06-13 11:19                                       ` Russell King - ARM Linux
2013-06-13 11:19                                         ` Russell King - ARM Linux
2013-06-13 11:50                                         ` Russell King - ARM Linux
2013-06-13 11:50                                           ` Russell King - ARM Linux
2013-06-13 13:03                                           ` Russell King - ARM Linux
2013-06-13 13:03                                             ` Russell King - ARM Linux
2013-06-14 14:23                                             ` Daniel Vetter
2013-06-14 14:23                                               ` Daniel Vetter
2013-06-14 14:42                                               ` Russell King - ARM Linux
2013-06-14 14:42                                                 ` Russell King - ARM Linux
2013-06-14 19:50                                                 ` Daniel Vetter
2013-06-14 19:50                                                   ` Daniel Vetter
2013-06-14 22:15                                                   ` Russell King - ARM Linux
2013-06-14 22:15                                                     ` Russell King - ARM Linux
2013-06-14 22:36                                                     ` Daniel Vetter
2013-06-14 22:36                                                       ` Daniel Vetter
2013-06-14 13:53                                           ` Daniel Vetter
2013-06-14 13:53                                             ` Daniel Vetter
2013-06-14 14:27                                             ` Russell King - ARM Linux
2013-06-14 14:27                                               ` Russell King - ARM Linux
2013-06-13 12:52                                         ` Rob Clark
2013-06-13 12:52                                           ` Rob Clark
2013-06-13 12:58                                           ` Daniel Vetter
2013-06-13 12:58                                             ` Daniel Vetter
2013-06-12 20:04                                   ` Rob Clark
2013-06-12 20:04                                     ` Rob Clark
2013-06-10 23:38                     ` Russell King - ARM Linux
2013-06-10 23:38                       ` Russell King - ARM Linux
2013-06-10 23:49                       ` Rob Clark
2013-06-10 23:49                         ` Rob Clark
2013-06-10 22:01         ` Daniel Vetter
2013-06-10 22:01           ` Daniel Vetter
2013-06-10 22:32           ` Russell King - ARM Linux
2013-06-10 22:32             ` Russell King - ARM Linux
2013-06-10 23:12             ` Rob Clark
2013-06-10 23:12               ` Rob Clark
2013-06-11  7:33             ` Daniel Vetter
2013-06-11  7:33               ` Daniel Vetter
2013-06-11  8:08           ` Ville Syrjälä
2013-06-11  8:08             ` Ville Syrjälä
2013-06-10 21:38     ` Russell King - ARM Linux
2013-06-10 21:38       ` Russell King - ARM Linux
2013-06-09 19:30 ` [PATCH RFC 3/8] drm/i2c: nxp-tda998x: fix EDID reading on TDA19988 devices Russell King
2013-06-09 19:30   ` Russell King
2013-06-09 19:31 ` [PATCH RFC 4/8] drm/i2c: nxp-tda998x: ensure VIP output mux is properly set Russell King
2013-06-09 19:31   ` Russell King
2013-06-09 19:32 ` [PATCH RFC 5/8] drm/i2c: nxp-tda998x: fix npix/nline programming Russell King
2013-06-09 19:32   ` Russell King
2013-06-09 20:02   ` Sebastian Hesselbarth
2013-06-09 20:02     ` Sebastian Hesselbarth
2013-06-09 19:34 ` [PATCH RFC 6/8] drm/i2c: nxp-tda998x: prepare for video input configuration Russell King
2013-06-09 19:34   ` Russell King
2013-06-09 19:35 ` [PATCH RFC 7/8] drm/i2c: nxp-tda998x: add video and audio " Russell King
2013-06-09 19:35   ` Russell King
2013-06-09 19:36 ` [PATCH RFC 8/8] DRM: Armada: add support for drm tda19988 driver Russell King
2013-06-09 19:36   ` Russell King
2013-06-09 19:43 ` [RFC v2 0/8] rmk's Dove DRM/TDA19988 Cubox driver Russell King - ARM Linux
2013-06-09 19:43   ` Russell King - ARM Linux
2013-06-10 22:47 ` [RFC v3 0/4] " Russell King - ARM Linux
2013-06-10 22:47   ` Russell King - ARM Linux
2013-06-10 22:48 ` [PATCH RFC v3 1/4] DRM: Armada: Add Armada DRM driver Russell King
2013-06-10 22:51   ` Russell King
2013-06-10 22:49 ` [PATCH RFC v3 2/4] DRM: Armada: Add support for hardware cursors Russell King
2013-06-10 22:49   ` Russell King
2013-06-10 22:50 ` [PATCH RFC v3 3/4] DRM: Armada: convert Armada hardware cursor support to RGB+transparency Russell King
2013-06-10 22:50   ` Russell King
2013-06-10 22:51 ` [PATCH RFC v3 4/4] DRM: Armada: convert hardware cursor support to 64x32 or 32x64 ARGB Russell King
2013-06-10 22:51   ` Russell King

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.