[PATCH] media/at91sam9x5-video: new driver for the high end overlay on at91sam9x5

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Uwe Kleine-König <u.kleine-koenig@xxxxxxxxxxxxxx>
---
Hello,

this is a driver that is still classified as work in progress. There are
some known problems with it, still following the "release early, release
often" principle I'd be happy to get some feed back from you.

Here is a list of know deficiencies:
 - There is a hardware constraint I only noticed while testing. The
   window/format config must only be updated once per (hardware) frame.
   Otherwise dma might just stop without the driver noticing.
 - device registration shouldn't happen in the driver. This should go
   either to arch/arm/mach-at91 or the (yet to be submitted) fb driver.
 - handle underflow errors
   (For that the video driver needs some integration into the fb driver
   as the underflow is reported in the fb's register space only)
   For that reason rotation only works after enabling the "I know what I'm
   doing" switch as rotation of a bigger video that can be played back
   smoothly without rotation completely flattens the machine.
 - clipping could be supported
 - alpha blending could be supported
 - handle more formats

Other than that there are a few questions marked with a tripple-X in the
code.

Thanks for your time,
Uwe
 drivers/media/video/Kconfig            |    8 +
 drivers/media/video/Makefile           |    1 +
 drivers/media/video/at91sam9x5-video.c | 1440 ++++++++++++++++++++++++++++++++
 4 files changed, 1453 insertions(+), 3 deletions(-)
 create mode 100644 drivers/media/video/at91sam9x5-video.c

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index 00f51dd..c937e4d 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -81,6 +81,14 @@ menuconfig VIDEO_CAPTURE_DRIVERS
 
 if VIDEO_CAPTURE_DRIVERS && VIDEO_V4L2
 
+config VIDEO_AT91SAM9X5
+	tristate "Support for AT91SAM9X5 Video"
+	depends on ARCH_AT91SAM9X5
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_DMA_CONTIG
+	help
+	  support for the "High End Overlay" found in Atmel's AT91SAM9X5 SoCs.
+
 config VIDEO_ADV_DEBUG
 	bool "Enable advanced debug functionality"
 	default n
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index ace5d8b..5a7620d 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_VIDEO_MXB) += mxb.o
 obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o
 obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o
 obj-$(CONFIG_VIDEO_TIMBERDALE)	+= timblogiw.o
+obj-$(CONFIG_VIDEO_AT91SAM9X5) += at91sam9x5-video.o
 
 obj-$(CONFIG_VIDEOBUF_GEN) += videobuf-core.o
 obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o
diff --git a/drivers/media/video/at91sam9x5-video.c b/drivers/media/video/at91sam9x5-video.c
new file mode 100644
index 0000000..1e5154b
--- /dev/null
+++ b/drivers/media/video/at91sam9x5-video.c
@@ -0,0 +1,1440 @@
+/*
+ * Copyright (C) 2011 Pengutronix
+ * Uwe Kleine-Koenig <u.kleine-koenig@xxxxxxxxxxxxxx>
+ *
+ * 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.
+ */
+
+/*
+ * XXX:
+ * - handle setting of global alpha
+ * - handle more formats
+ * - complete this list :-)
+ */
+
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define debug(fmt, ...)
+
+#define DRIVER_NAME "at91sam9x5-video"
+
+#define REG_HEOCHER		0x00
+#define REG_HEOCHER_CHEN		0x00000001
+#define REG_HEOCHER_UPDATEEN		0x00000002
+#define REG_HEOCHER_A2QEN		0x00000004
+
+#define REG_HEOCHDR		0x04
+#define REG_HEOCHDR_CHDIS		0x00000001
+#define REG_HEOCHDR_CHRST		0x00000100
+
+#define REG_HEOCHSR		0x08
+#define REG_HEOCHSR_CHSR		0x00000001
+#define REG_HEOCHSR_UPDATESR		0x00000002
+#define REG_HEOCHSR_A2QSR		0x00000004
+
+#define REG_HEOIER		0x0c
+#define REG_HEOIDR		0x10
+#define REG_HEOIMR		0x14
+#define REG_HEOISR		0x18
+#define REG_HEOIxR_DMA			0x00000004
+#define REG_HEOIxR_DSCR			0x00000008
+#define REG_HEOIxR_ADD			0x00000010
+#define REG_HEOIxR_DONE			0x00000020
+#define REG_HEOIxR_OVR			0x00000040
+#define REG_HEOIxR_UDMA			0x00000400
+#define REG_HEOIxR_UDSCR		0x00000800
+#define REG_HEOIxR_UADD			0x00001000
+#define REG_HEOIxR_UDONE		0x00002000
+#define REG_HEOIxR_UOVR			0x00004000
+#define REG_HEOIxR_VDMA			0x00040000
+#define REG_HEOIxR_VDSCR		0x00080000
+#define REG_HEOIxR_VADD			0x00100000
+#define REG_HEOIxR_VDONE		0x00200000
+#define REG_HEOIxR_VOVR			0x00400000
+
+#define REG_HEOHEAD		0x1c
+#define REG_HEOUHEAD		0x2c
+#define REG_HEOVHEAD		0x3c
+
+#define REG_HEOADDR		0x20
+#define REG_HEOUADDR		0x30
+#define REG_HEOVADDR		0x40
+
+#define REG_HEOCTRL		0x24
+#define REG_HEOUCTRL		0x34
+#define REG_HEOVCTRL		0x44
+#define REG_HEOxCTRL_DFETCH		0x00000001
+#define REG_HEOCTRL_LFETCH		0x00000002
+#define REG_HEOxCTRL_DMAIEN		0x00000004
+#define REG_HEOxCTRL_DSCRIEN		0x00000008
+#define REG_HEOxCTRL_ADDIEN		0x00000010
+#define REG_HEOxCTRL_DONEIEN		0x00000020
+
+#define REG_HEONEXT		0x28
+#define REG_HEOUNEXT		0x38
+#define REG_HEOVNEXT		0x48
+
+#define REG_HEOCFG0		0x4c
+#define REG_HEOCFG0_DLBO		0x00000100
+#define REG_HEOCFG0_BLEN		0x00000030
+#define REG_HEOCFG0_BLEN_INCR1			0x00000000
+#define REG_HEOCFG0_BLEN_INCR4			0x00000010
+#define REG_HEOCFG0_BLEN_INCR8			0x00000020
+#define REG_HEOCFG0_BLEN_INCR16			0x00000030
+#define REG_HEOCFG0_BLENUV		0x000000c0
+#define REG_HEOCFG0_BLENUV_INCR1		0x00000000
+#define REG_HEOCFG0_BLENUV_INCR4		0x00000040
+#define REG_HEOCFG0_BLENUV_INCR8		0x00000080
+#define REG_HEOCFG0_BLENUV_INCR16		0x000000c0
+
+#define REG_HEOCFG1		0x50
+#define REG_HEOCFG1_CLUTEN		0x00000001
+#define REG_HEOCFG1_YUVEN		0x00000002
+#define REG_HEOCFG1_YUVMODE_12YCBCRP	0x00008000
+
+#define REG_HEOCFG2		0x54
+#define REG_HEOCFG2_XPOS		0x000007ff
+#define REG_HEOCFG2_YPOS		0x07ff0000
+
+#define REG_HEOCFG3		0x58
+#define REG_HEOCFG3_XSIZE		0x000007ff
+#define REG_HEOCFG3_YSIZE		0x07ff0000
+
+#define REG_HEOCFG4		0x5c
+#define REG_HEOCFG4_XMEMSIZE		0x000007ff
+#define REG_HEOCFG4_YMEMSIZE		0x07ff0000
+
+#define REG_HEOCFG5		0x60
+#define REG_HEOCFG5_XSTRIDE		0xffffffff
+
+#define REG_HEOCFG6		0x64
+#define REG_HEOCFG6_PSTRIDE		0xffffffff
+
+#define REG_HEOCFG7		0x68
+#define REG_HEOCFG7_UVXSTRIDE		0xffffffff
+
+#define REG_HEOCFG8		0x6c
+#define REG_HEOCFG8_UVPSTRIDE		0xffffffff
+
+#define REG_HEOCFG9		0x70
+#define REG_HEOCFG10		0x74
+#define REG_HEOCFG11		0x78
+
+#define REG_HEOCFG12		0x7c
+#define REG_HEOCFG12_CRKEY		0x00000001
+#define REG_HEOCFG12_INV		0x00000002
+#define REG_HEOCFG12_ITER2BL		0x00000004
+#define REG_HEOCFG12_ITER		0x00000008
+#define REG_HEOCFG12_REVALPHA		0x00000010
+#define REG_HEOCFG12_GAEN		0x00000020
+#define REG_HEOCFG12_LAEN		0x00000040
+#define REG_HEOCFG12_OVR		0x00000080
+#define REG_HEOCFG12_DMA		0x00000100
+#define REG_HEOCFG12_REP		0x00000200
+#define REG_HEOCFG12_DSTKEY		0x00000400
+#define REG_HEOCFG12_VIDPRI		0x00001000
+#define REG_HEOCFG12_GA			0x00ff0000
+
+#define REG_HEOCFG13		0x80
+#define REG_HEOCFG13_XFACTOR		0x00001fff
+#define REG_HEOCFG13_YFACTOR		0x1fff0000
+#define REG_HEOCFG13_SCALEN		0x80000000
+
+#define REG_HEOCFG14		0x84
+#define REG_HEOCFG15		0x88
+#define REG_HEOCFG16		0x8c
+
+#define valtomask(val, mask)	(((val) << __ffs((mask))) & (mask))
+#define valfrommask(val, mask)	(((val) & (mask)) >> __ffs((mask)))
+
+struct at91sam9x5_video_pdata {
+	u16 base_width;
+	u16 base_height;
+};
+
+struct at91sam9x5_video_bufinfo {
+	struct vb2_buffer *vb;
+	unsigned u_planeno, v_planeno;
+	unsigned long plane_size[3];
+};
+
+struct at91sam9x5_video_priv {
+	struct platform_device *pdev;
+
+	/* framebuffer stuff */
+	struct notifier_block fb_notifier;
+	struct fb_info *fbinfo;
+
+	struct video_device *video_dev;
+
+	void __iomem *regbase;
+	unsigned int irq;
+
+	struct vb2_queue queue;
+	void *alloc_ctx;
+
+	struct at91sam9x5_video_bufinfo cur, next;
+
+	/* protects the members after lock and hardware access */
+	spinlock_t lock;
+
+	enum {
+		/* DMA not running */
+		at91sam9x5_video_HW_IDLE,
+		/* DMA running, unless cfgstate is BAD */
+		at91sam9x5_video_HW_RUNNING,
+	} hwstate;
+
+	enum {
+		at91sam9x5_video_CFG_GOOD,
+		/* the shadow registers need an update */
+		at91sam9x5_video_CFG_GOOD_LATCH,
+		at91sam9x5_video_CFG_BAD,
+	} cfgstate;
+
+	/* if true the vid_out config in hardware doesn't match sw config */
+	int cfgupdate;
+
+	int valid_config;
+
+	struct v4l2_pix_format fmt_vid_out_cur, fmt_vid_out_next;
+
+	int rotation;
+
+	struct v4l2_window fmt_vid_overlay;
+
+	/*
+	 * For YUV formats Y data is always in plane 0. U, V are either both in
+	 * 0, both in 1, or U in 1 or V in 2. -1 for formats that don't use U
+	 * and V.
+	 */
+	int u_planeno, v_planeno;
+
+	unsigned long plane_size[3];
+
+	/*
+	 * These are the offsets into the buffers to start the hardware for.
+	 * Depending on rotation and overlay position this is more or less ugly
+	 * to calculate. (y_offset is used for rgb data, too.)
+	 */
+	u32 y_offset, u_offset, v_offset;
+
+	u32 irqstat;
+};
+
+static u32 at91sam9x5_video_read32(struct at91sam9x5_video_priv *priv,
+		size_t offset)
+{
+	/* XXX: really use the __raw variants? */
+	return __raw_readl(priv->regbase + offset);
+}
+
+static void at91sam9x5_video_write32(struct at91sam9x5_video_priv *priv,
+		size_t offset, u32 val)
+{
+	debug("$%x := %08x, $08 == %08x\n", offset, val,
+			at91sam9x5_video_read32(priv, REG_HEOCHSR));
+	__raw_writel(val, priv->regbase + offset);
+	debug("$08 == %08x\n", at91sam9x5_video_read32(priv, REG_HEOCHSR));
+}
+
+static int __at91sam9x5_video_buf_in_use(struct at91sam9x5_video_priv *priv,
+		struct at91sam9x5_video_bufinfo *bi,
+		size_t heoaddr_offset, unsigned planeno)
+{
+	if (planeno >= 0) {
+		u32 heoaddr = at91sam9x5_video_read32(priv, heoaddr_offset);
+		dma_addr_t plane_paddr =
+			vb2_dma_contig_plane_paddr(bi->vb, planeno);
+
+		if (heoaddr - plane_paddr <= bi->plane_size[planeno])
+			return 1;
+	}
+
+	return 0;
+}
+
+
+static int at91sam9x5_video_buf_in_use(struct at91sam9x5_video_priv *priv,
+		struct at91sam9x5_video_bufinfo *bi)
+{
+	if (__at91sam9x5_video_buf_in_use(priv, bi, REG_HEOADDR, 0))
+		return 1;
+	if (__at91sam9x5_video_buf_in_use(priv, bi,
+				REG_HEOUADDR, bi->u_planeno))
+		return 1;
+	if (__at91sam9x5_video_buf_in_use(priv, bi,
+				REG_HEOVADDR, bi->v_planeno))
+		return 1;
+
+	return 0;
+}
+
+static u32 at91sam9x5_video_handle_irqstat(struct at91sam9x5_video_priv *priv)
+{
+	u32 heoisr = at91sam9x5_video_read32(priv, REG_HEOISR);
+
+	debug("cur=%p, next=%p, heoisr=%08x\n", priv->cur.vb,
+			priv->next.vb, heoisr);
+	debug("cfgupdate=%d hwstate=%d cfgstate=%d\n",
+			priv->cfgupdate, priv->hwstate, priv->cfgstate);
+
+	if (!priv->cur.vb) {
+		priv->cur = priv->next;
+		priv->next.vb = NULL;
+	}
+
+	if (priv->hwstate == at91sam9x5_video_HW_IDLE &&
+			!(at91sam9x5_video_read32(priv, REG_HEOCHSR) &
+				REG_HEOCHSR_CHSR)) {
+		if (priv->cur.vb) {
+			vb2_buffer_done(priv->cur.vb, VB2_BUF_STATE_DONE);
+			priv->cur.vb = NULL;
+		}
+
+		if (priv->next.vb) {
+			vb2_buffer_done(priv->next.vb, VB2_BUF_STATE_DONE);
+			priv->next.vb = NULL;
+		}
+
+		at91sam9x5_video_write32(priv, REG_HEOIDR,
+				REG_HEOIxR_ADD | REG_HEOIxR_DMA |
+				REG_HEOIxR_UADD | REG_HEOIxR_UDMA |
+				REG_HEOIxR_VADD | REG_HEOIxR_VDMA);
+
+	} else if (priv->cur.vb && priv->next.vb) {
+		int hwrunning = 1;
+		if (priv->cfgstate == at91sam9x5_video_CFG_BAD &&
+				!(at91sam9x5_video_read32(priv, REG_HEOCHSR) &
+					REG_HEOCHSR_CHSR))
+			hwrunning = 0;
+
+		if (!hwrunning || !at91sam9x5_video_buf_in_use(priv,
+					&priv->cur)) {
+			vb2_buffer_done(priv->cur.vb, VB2_BUF_STATE_DONE);
+			priv->cur = priv->next;
+			priv->next.vb = NULL;
+		}
+	} else if (priv->next.vb) {
+		priv->cur = priv->next;
+		priv->next.vb = NULL;
+	}
+
+	return heoisr;
+}
+
+static irqreturn_t at91sam9x5_video_irq(int irq, void *data)
+{
+	struct at91sam9x5_video_priv *priv = data;
+	unsigned long flags;
+	u32 handled, heoimr;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	heoimr = at91sam9x5_video_read32(priv, REG_HEOIMR);
+	handled = at91sam9x5_video_handle_irqstat(priv);
+
+	debug("%x, HEOCHSR = %08x\n", handled,
+			at91sam9x5_video_read32(priv, REG_HEOCHSR));
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	if (handled & heoimr)
+		return IRQ_HANDLED;
+	else
+		return IRQ_NONE;
+}
+
+static inline int sign(int x)
+{
+	if (x > 0)
+		return 1;
+	else if (x < 0)
+		return -1;
+	else
+		return 0;
+}
+
+static void at91sam9x5_video_show_buf(struct at91sam9x5_video_priv *priv,
+		struct vb2_buffer *vb)
+{
+	dma_addr_t buffer = vb2_dma_contig_plane_paddr(vb, 0);
+	void *vaddr = vb2_plane_vaddr(vb, 0);
+	struct v4l2_pix_format *pix = &priv->fmt_vid_out_cur;
+	/* XXX: format dependant */
+	size_t offset_dmadesc = ALIGN(pix->width * pix->height +
+			ALIGN(pix->width, 2) * ALIGN(pix->height, 2) / 2, 32);
+	u32 *dmadesc = vaddr + offset_dmadesc;
+	u32 heocher;
+
+	if (priv->cfgstate == at91sam9x5_video_CFG_GOOD_LATCH) {
+		heocher = REG_HEOCHER_UPDATEEN;
+		priv->cfgstate = at91sam9x5_video_CFG_GOOD;
+	} else {
+		BUG_ON(priv->cfgstate != at91sam9x5_video_CFG_GOOD);
+		heocher = 0;
+	}
+
+	debug("vout=%ux%u, heocher=%08x\n", pix->width, pix->height, heocher);
+
+	dmadesc[0] = buffer + priv->y_offset;
+	dmadesc[1] = REG_HEOxCTRL_DFETCH;
+	dmadesc[2] = buffer + offset_dmadesc;
+
+	if (priv->u_planeno >= 0) {
+		dmadesc[3] = vb2_dma_contig_plane_paddr(vb, priv->u_planeno) +
+			priv->u_offset;
+		dmadesc[4] = REG_HEOxCTRL_DFETCH;
+		dmadesc[5] = buffer + offset_dmadesc + 3 * 4;
+	}
+
+	if (priv->v_planeno >= 0) {
+		dmadesc[6] = vb2_dma_contig_plane_paddr(vb, priv->v_planeno) +
+			priv->v_offset;
+		dmadesc[7] = REG_HEOxCTRL_DFETCH;
+		dmadesc[8] = buffer + offset_dmadesc + 6 * 4;
+	}
+
+
+	debug("HEOCHSR = %08x\n", at91sam9x5_video_read32(priv, REG_HEOCHSR));
+	if (likely(priv->hwstate == at91sam9x5_video_HW_RUNNING)) {
+
+		at91sam9x5_video_write32(priv, REG_HEOHEAD, dmadesc[2]);
+
+		if (priv->u_planeno >= 0)
+			at91sam9x5_video_write32(priv,
+					REG_HEOUHEAD, dmadesc[5]);
+
+		if (priv->v_planeno >= 0)
+			at91sam9x5_video_write32(priv,
+					REG_HEOVHEAD, dmadesc[8]);
+
+		at91sam9x5_video_write32(priv,
+				REG_HEOCHER, heocher | REG_HEOCHER_A2QEN);
+
+	} else {
+
+		at91sam9x5_video_write32(priv, REG_HEOADDR, dmadesc[0]);
+		at91sam9x5_video_write32(priv, REG_HEOCTRL, dmadesc[1]);
+		at91sam9x5_video_write32(priv, REG_HEONEXT, dmadesc[2]);
+
+		if (priv->u_planeno >= 0) {
+			at91sam9x5_video_write32(priv,
+					REG_HEOUADDR, dmadesc[3]);
+			at91sam9x5_video_write32(priv,
+					REG_HEOUCTRL, dmadesc[4]);
+			at91sam9x5_video_write32(priv,
+					REG_HEOUNEXT, dmadesc[5]);
+		}
+
+		if (priv->v_planeno >= 0) {
+			at91sam9x5_video_write32(priv,
+					REG_HEOVADDR, dmadesc[6]);
+			at91sam9x5_video_write32(priv,
+					REG_HEOVCTRL, dmadesc[7]);
+			at91sam9x5_video_write32(priv,
+					REG_HEOVNEXT, dmadesc[8]);
+		}
+
+		at91sam9x5_video_write32(priv, REG_HEOCHER,
+				heocher | REG_HEOCHER_CHEN);
+
+		priv->hwstate = at91sam9x5_video_HW_RUNNING;
+	}
+
+	if (priv->cur.vb && at91sam9x5_video_buf_in_use(priv, &priv->cur)) {
+		if (priv->next.vb) {
+			/* drop next; XXX: is this an error? */
+			debug("drop %p\n", priv->next.vb);
+			vb2_buffer_done(priv->next.vb, VB2_BUF_STATE_ERROR);
+		}
+	} else {
+		if (priv->cur.vb)
+			vb2_buffer_done(priv->cur.vb, VB2_BUF_STATE_DONE);
+
+		priv->cur = priv->next;
+	}
+	priv->next.vb = vb;
+	priv->next.u_planeno = priv->u_planeno;
+	priv->next.v_planeno = priv->v_planeno;
+	priv->next.plane_size[0] = priv->plane_size[0];
+	priv->next.plane_size[1] = priv->plane_size[1];
+	priv->next.plane_size[2] = priv->plane_size[2];
+}
+
+static int experimental;
+module_param(experimental, bool, 0644);
+MODULE_PARM_DESC(experimental, "enable experimental features");
+
+static void at91sam9x5_video_params(unsigned width, unsigned height,
+		int rotation, u32 *xstride, u32 *pstride, u32 *tloffset)
+{
+/* offset of pixel at (x, y) in the buffer */
+#define po(x, y) ((x) + width * (y))
+
+	/* offsets of the edges in counter-clockwise order */
+	const unsigned e[] = {
+		po(0, 0),
+		po(0, height - 1),
+		po(width - 1, height - 1),
+		po(width - 1, 0),
+	};
+
+	/*
+	 * offsets of the pixels next to the corresponding edges
+	 * If edge[i] goes to the top left corner, edge_neighbour[i] is
+	 * located just below of edge[i].
+	 */
+	const unsigned en[] = {
+		po(0, 1),
+		po(1, height - 1),
+		po(width - 1, height - 2),
+		po(width - 2, 0),
+	};
+
+#define ro(r) ((rotation + (r)) % 4)
+
+	*xstride = en[ro(0)] - e[ro(3)];
+	*pstride = e[ro(3)] - en[ro(3)];
+	*tloffset = e[ro(0)];
+}
+
+static void at91sam9x5_video_update_config_real(
+		struct at91sam9x5_video_priv *priv)
+{
+	struct v4l2_pix_format *pix = &priv->fmt_vid_out_cur;
+	struct v4l2_window *win = &priv->fmt_vid_overlay;
+	struct v4l2_rect *rect = &win->w;
+	/* XXX: check for overflow? */
+	s32 right = rect->left + rect->width, bottom = rect->top + rect->height;
+
+	unsigned hwxpos, hwypos, hwxsize, hwysize;
+	unsigned hwxmem_size, hwymem_size;
+	s32 hwxstride, hwpstride;
+	s32 hwuvxstride, hwuvpstride;
+	s32 rotated_pixwidth, rotated_pixheight;
+
+	debug("vout=%ux%u, ovl=(%d,%d)+(%d,%d)\n", pix->width, pix->height,
+			rect->left, rect->top, rect->width, rect->height);
+
+	if (!experimental && priv->rotation) {
+		dev_info(&priv->video_dev->dev, "disable rotation\n");
+		priv->rotation = 0;
+	}
+
+	if (rect->left < 0)
+		hwxpos = 0;
+	else
+		hwxpos = rect->left;
+
+	if (rect->top < 0)
+		hwypos = 0;
+	else
+		hwypos = rect->top;
+
+	if (right > priv->fbinfo->var.xres)
+		hwxsize = priv->fbinfo->var.xres - hwxpos;
+	else
+		hwxsize = right - hwxpos;
+
+	if (bottom > priv->fbinfo->var.yres)
+		hwysize = priv->fbinfo->var.yres - hwypos;
+	else
+		hwysize = bottom - hwypos;
+
+	at91sam9x5_video_write32(priv, REG_HEOCFG2,
+			valtomask(hwxpos, REG_HEOCFG2_XPOS) |
+			valtomask(hwypos, REG_HEOCFG2_YPOS));
+
+	at91sam9x5_video_write32(priv, REG_HEOCFG3,
+			valtomask(hwxsize - 1, REG_HEOCFG3_XSIZE) |
+			valtomask(hwysize - 1, REG_HEOCFG3_YSIZE));
+
+	/* XXX:
+	 *  - clipping
+	 */
+	at91sam9x5_video_write32(priv, REG_HEOCFG1,
+			REG_HEOCFG1_YUVMODE_12YCBCRP |
+			REG_HEOCFG1_YUVEN);
+	at91sam9x5_video_write32(priv, REG_HEOCFG12,
+			REG_HEOCFG12_GAEN |
+			REG_HEOCFG12_OVR |
+			REG_HEOCFG12_DMA |
+			REG_HEOCFG12_REP |
+			REG_HEOCFG12_GA);
+
+#define vx(pos) xedge[(priv->rotation + pos) % 4]
+#define vy(pos) yedge[(priv->rotation + pos) % 4]
+
+	if (priv->rotation & 1) {
+		rotated_pixwidth = pix->height;
+		rotated_pixheight = pix->width;
+	} else {
+		rotated_pixwidth = pix->width;
+		rotated_pixheight = pix->height;
+	}
+
+	hwxmem_size = rotated_pixwidth * hwxsize / rect->width;
+	hwymem_size = rotated_pixheight * hwysize / rect->height;
+
+	at91sam9x5_video_write32(priv, REG_HEOCFG4,
+			valtomask(hwxmem_size - 1, REG_HEOCFG4_XMEMSIZE) |
+			valtomask(hwymem_size - 1, REG_HEOCFG4_YMEMSIZE));
+
+	at91sam9x5_video_write32(priv, REG_HEOCFG13,
+			REG_HEOCFG13_SCALEN |
+			valtomask(1024 * hwxmem_size / hwxsize,
+				REG_HEOCFG13_XFACTOR) |
+			valtomask(1024 * hwymem_size / hwysize,
+				REG_HEOCFG13_YFACTOR));
+
+	at91sam9x5_video_params(pix->width, pix->height, priv->rotation,
+			&hwxstride, &hwpstride, &priv->y_offset);
+
+	/* XXX: format-dependant */
+	at91sam9x5_video_params(DIV_ROUND_UP(pix->width, 2),
+			DIV_ROUND_UP(pix->height, 2), priv->rotation,
+			&hwuvxstride, &hwuvpstride, &priv->u_offset);
+
+	at91sam9x5_video_write32(priv, REG_HEOCFG5,
+			valtomask(hwxstride - 1, REG_HEOCFG5_XSTRIDE));
+	at91sam9x5_video_write32(priv, REG_HEOCFG6,
+			valtomask(hwpstride - 1, REG_HEOCFG6_PSTRIDE));
+
+	at91sam9x5_video_write32(priv, REG_HEOCFG7,
+			valtomask(hwuvxstride - 1, REG_HEOCFG7_UVXSTRIDE));
+	at91sam9x5_video_write32(priv, REG_HEOCFG8,
+			valtomask(hwuvpstride - 1, REG_HEOCFG8_UVPSTRIDE));
+
+	/* XXX: format dependant */
+	priv->u_planeno = 0;
+	priv->v_planeno = 0;
+	priv->u_offset += pix->width * pix->height;
+	priv->v_offset = priv->u_offset +
+		DIV_ROUND_UP(pix->width, 2) * DIV_ROUND_UP(pix->height, 2);
+
+	/* XXX: evaluate pix->colorspace */
+	at91sam9x5_video_write32(priv, REG_HEOCFG14, 0x4c900091);
+	at91sam9x5_video_write32(priv, REG_HEOCFG15, 0x7a5f5090);
+	at91sam9x5_video_write32(priv, REG_HEOCFG16, 0x40040890);
+}
+
+static void at91sam9x5_video_update_config(struct at91sam9x5_video_priv *priv,
+		int overlay_only)
+{
+	debug("cfgupdate=%d overlay_only=%d\n", priv->cfgupdate, overlay_only);
+
+	at91sam9x5_video_handle_irqstat(priv);
+
+	if (priv->cfgupdate || overlay_only) {
+		struct v4l2_pix_format *pix = &priv->fmt_vid_out_cur;
+		struct v4l2_window *win = &priv->fmt_vid_overlay;
+		struct v4l2_rect *rect = &win->w;
+
+		if (!overlay_only) {
+			*pix = priv->fmt_vid_out_next;
+			priv->cfgupdate = 0;
+		}
+
+		/* XXX: handle clipping */
+		if (rect->width <= 0 || rect->height <= 0 ||
+				/* vid_out is set */
+				pix->width <= 0 ||
+				pix->height <= 0 ||
+				/* window is partly invisible or too small */
+				rect->left < 0 ||
+				rect->top < 0 ||
+				rect->left >= (int)priv->fbinfo->var.xres - 5 ||
+				rect->top >= (int)priv->fbinfo->var.yres - 5 ||
+				rect->left + rect->width >
+					(int)priv->fbinfo->var.xres ||
+				rect->top + rect->height >
+					(int)priv->fbinfo->var.yres) {
+
+			if (priv->cfgstate == at91sam9x5_video_CFG_GOOD ||
+					priv->cfgstate ==
+					at91sam9x5_video_CFG_GOOD_LATCH)
+				at91sam9x5_video_write32(priv,
+						REG_HEOCHDR, REG_HEOCHDR_CHDIS);
+
+			priv->cfgstate = at91sam9x5_video_CFG_BAD;
+		} else {
+			at91sam9x5_video_update_config_real(priv);
+
+			debug("hwstate=%d cfgstate=%d\n",
+					priv->hwstate, priv->cfgstate);
+			if (overlay_only && priv->hwstate ==
+					at91sam9x5_video_HW_RUNNING) {
+				if (priv->cfgstate ==
+						at91sam9x5_video_CFG_BAD) {
+					priv->cfgstate =
+						at91sam9x5_video_CFG_GOOD_LATCH;
+					priv->hwstate =
+						at91sam9x5_video_HW_IDLE;
+
+					at91sam9x5_video_show_buf(priv,
+							priv->cur.vb);
+				} else
+					at91sam9x5_video_write32(priv,
+							REG_HEOCHER,
+							REG_HEOCHER_UPDATEEN);
+			} else
+				priv->cfgstate =
+					at91sam9x5_video_CFG_GOOD_LATCH;
+		}
+
+	}
+}
+
+static int at91sam9x5_video_vb_queue_setup(struct vb2_queue *q,
+		unsigned int *num_buffers, unsigned int *num_planes,
+		unsigned long sizes[], void *alloc_ctxs[])
+{
+	struct at91sam9x5_video_priv *priv =
+		container_of(q, struct at91sam9x5_video_priv, queue);
+	struct v4l2_pix_format *pix = &priv->fmt_vid_out_next;
+
+	debug("vout=%ux%u\n", pix->width, pix->height);
+
+	/* XXX */
+	*num_planes = 1;
+
+	/*
+	 * The last 9 (aligned) words are used for the 3 dma descriptors (3
+	 * 32-bit words each). The additional 32 bits are for alignment.
+	 * XXX: is that allowed and done right?
+	 * XXX: format-dependant
+	 */
+	sizes[0] = pix->width * pix->height +
+		ALIGN(pix->width, 2) * ALIGN(pix->height, 2) / 2 +
+		10 * 32;
+	priv->plane_size[0] = sizes[0];
+
+	alloc_ctxs[0] = priv->alloc_ctx;
+
+	return 0;
+}
+
+static void at91sam9x5_video_vb_wait_prepare(struct vb2_queue *q)
+{
+	struct at91sam9x5_video_priv *priv =
+		container_of(q, struct at91sam9x5_video_priv, queue);
+	unsigned long flags;
+
+	debug("cfgupdate=%d hwstate=%d cfgstate=%d\n",
+			priv->cfgupdate, priv->hwstate, priv->cfgstate);
+	debug("bufs=%p,%p\n", priv->cur.vb, priv->next.vb);
+	spin_lock_irqsave(&priv->lock, flags);
+
+	at91sam9x5_video_handle_irqstat(priv);
+
+	at91sam9x5_video_write32(priv, REG_HEOIER,
+			REG_HEOIxR_ADD | REG_HEOIxR_DMA |
+			REG_HEOIxR_UADD | REG_HEOIxR_UDMA |
+			REG_HEOIxR_VADD | REG_HEOIxR_VDMA);
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void at91sam9x5_video_vb_wait_finish(struct vb2_queue *q)
+{
+	struct at91sam9x5_video_priv *priv =
+		container_of(q, struct at91sam9x5_video_priv, queue);
+	unsigned long flags;
+
+	debug("cfgupdate=%d hwstate=%d cfgstate=%d\n",
+			priv->cfgupdate, priv->hwstate, priv->cfgstate);
+	debug("bufs=%p,%p\n", priv->cur.vb, priv->next.vb);
+	spin_lock_irqsave(&priv->lock, flags);
+
+	at91sam9x5_video_write32(priv, REG_HEOIDR,
+			REG_HEOIxR_ADD | REG_HEOIxR_DMA |
+			REG_HEOIxR_UADD | REG_HEOIxR_UDMA |
+			REG_HEOIxR_VADD | REG_HEOIxR_VDMA);
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int at91sam9x5_video_vb_buf_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_queue *q = vb->vb2_queue;
+	struct at91sam9x5_video_priv *priv =
+		container_of(q, struct at91sam9x5_video_priv, queue);
+	struct v4l2_pix_format *pix = &priv->fmt_vid_out_cur;
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	if (priv->cfgupdate)
+		pix = &priv->fmt_vid_out_next;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	debug("vout=%ux%u\n", pix->width, pix->height);
+	debug("buflen=%u\n", vb->v4l2_planes[0].length);
+
+	/* XXX: format-dependant */
+	if (vb->v4l2_planes[0].length < pix->width * pix->height +
+			ALIGN(pix->width, 2) * ALIGN(pix->height, 2) / 2 +
+			10 * 32)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void at91sam9x5_video_vb_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_queue *q = vb->vb2_queue;
+	struct at91sam9x5_video_priv *priv =
+		container_of(q, struct at91sam9x5_video_priv, queue);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	at91sam9x5_video_update_config(priv, 0);
+
+	switch (priv->cfgstate) {
+	case at91sam9x5_video_CFG_GOOD:
+	case at91sam9x5_video_CFG_GOOD_LATCH:
+		/* show_buf takes care of the eventual hwstate update */
+		at91sam9x5_video_show_buf(priv, vb);
+		break;
+
+	case at91sam9x5_video_CFG_BAD:
+		vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+		priv->hwstate = at91sam9x5_video_HW_RUNNING;
+		break;
+	}
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+const struct vb2_ops at91sam9x5_video_vb_ops = {
+	.queue_setup = at91sam9x5_video_vb_queue_setup,
+
+	.wait_prepare = at91sam9x5_video_vb_wait_prepare,
+	.wait_finish = at91sam9x5_video_vb_wait_finish,
+
+	.buf_prepare = at91sam9x5_video_vb_buf_prepare,
+	.buf_queue = at91sam9x5_video_vb_buf_queue,
+};
+
+static int at91sam9x5_video_vidioc_querycap(struct file *filp,
+		void *fh, struct v4l2_capability *cap)
+{
+	strcpy(cap->driver, DRIVER_NAME);
+	cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING |
+		V4L2_CAP_VIDEO_OVERLAY;
+
+	/* XXX */
+	cap->version = 0;
+	cap->card[0] = '\0';
+	cap->bus_info[0] = '\0';
+
+	return 0;
+}
+
+static int at91sam9x5_video_vidioc_g_fmt_vid_out(struct file *filp,
+		void *fh, struct v4l2_format *f)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	unsigned long flags;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	f->fmt.pix = priv->fmt_vid_out_next;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+	return 0;
+}
+
+static int at91sam9x5_video_vidioc_s_fmt_vid_out(struct file *filp,
+		void *fh, struct v4l2_format *f)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	unsigned long flags;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
+
+	if (pix->pixelformat != V4L2_PIX_FMT_YUV420)
+		return -EINVAL;
+
+	debug("vout=%ux%u\n", pix->width, pix->height);
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	priv->fmt_vid_out_next = *pix;
+
+	priv->cfgupdate = 1;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
+static int at91sam9x5_video_vidioc_g_fmt_vid_overlay(struct file *filp,
+		void *fh, struct v4l2_format *f)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	unsigned long flags;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+		return -EINVAL;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	f->fmt.win = priv->fmt_vid_overlay;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
+static int at91sam9x5_video_vidioc_s_fmt_vid_overlay(struct file *filp,
+		void *fh, struct v4l2_format *f)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	struct v4l2_window *win = &f->fmt.win;
+	unsigned long flags;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+		return -EINVAL;
+
+	debug("rect=(%d,%d)+(%d,%d)\n",
+			win->w.left, win->w.top, win->w.width, win->w.height);
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	priv->fmt_vid_overlay = *win;
+
+	at91sam9x5_video_update_config(priv, 1);
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
+static int at91sam9x5_video_vidioc_enum_fmt_vid_out(struct file *filp,
+		void *fh, struct v4l2_fmtdesc *f)
+{
+	/* XXX: support more formats */
+	if (f->index > 0)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_PIX_FMT_YUV420;
+	return 0;
+}
+
+static int at91sam9x5_video_vidioc_reqbufs(struct file *filp,
+		void *fh, struct v4l2_requestbuffers *b)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	struct vb2_queue *q = &priv->queue;
+
+	if (b->type != q->type) {
+		dev_err(&priv->pdev->dev, "invalid buffer type (%d != %d)\n",
+				b->type, q->type);
+		return -EINVAL;
+	}
+
+	return vb2_reqbufs(q, b);
+}
+
+static int at91sam9x5_video_vidioc_querybuf(struct file *filp,
+		void *fh, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+
+	return vb2_querybuf(&priv->queue, b);
+}
+
+static int at91sam9x5_video_vidioc_qbuf(struct file *filp,
+		void *fh, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+
+	return vb2_qbuf(&priv->queue, b);
+}
+
+static int at91sam9x5_video_vidioc_dqbuf(struct file *filp,
+		void *fh, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = filp->private_data;
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+
+	return vb2_dqbuf(&priv->queue, b, filp->f_flags & O_NONBLOCK);
+}
+
+static int at91sam9x5_video_vidioc_streamon(struct file *filp,
+		void *fh, enum v4l2_buf_type type)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+
+	return vb2_streamon(&priv->queue, type);
+}
+
+static int at91sam9x5_video_vidioc_streamoff(struct file *filp,
+		void *fh, enum v4l2_buf_type type)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	/* disable channel */
+	at91sam9x5_video_write32(priv, REG_HEOCHDR, REG_HEOCHDR_CHDIS);
+
+	at91sam9x5_video_handle_irqstat(priv);
+
+	if (priv->cur.vb)
+		at91sam9x5_video_write32(priv, REG_HEOIER,
+				REG_HEOIxR_ADD | REG_HEOIxR_DMA |
+				REG_HEOIxR_UADD | REG_HEOIxR_UDMA |
+				REG_HEOIxR_VADD | REG_HEOIxR_VDMA);
+
+	priv->hwstate = at91sam9x5_video_HW_IDLE;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return vb2_streamoff(&priv->queue, type);
+}
+
+static int at91sam9x5_video_vidioc_queryctrl(struct file *filp, void *fh,
+		struct v4l2_queryctrl *a)
+{
+	int ret;
+
+	switch (a->id) {
+	case V4L2_CID_ROTATE:
+		ret = v4l2_ctrl_query_fill(a, 0, 270, 90, 0);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int at91sam9x5_video_vidioc_g_ctrl(struct file *filp, void *fh,
+		struct v4l2_control *a)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	int ret = 0;
+
+	switch (a->id) {
+	case V4L2_CID_ROTATE:
+		a->value = 90 * priv->rotation;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int at91sam9x5_video_vidioc_s_ctrl(struct file *filp, void *fh,
+		struct v4l2_control *a)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+	int ret;
+	unsigned long flags;
+
+	switch (a->id) {
+	case V4L2_CID_ROTATE:
+		if (a->value / 90 * 90 != a->value ||
+				(a->value / 90) % 4 != a->value / 90) {
+			ret = -EINVAL;
+		} else {
+			debug("rotation: %d\n", a->value);
+			spin_lock_irqsave(&priv->lock, flags);
+			priv->rotation = a->value / 90;
+			at91sam9x5_video_update_config(priv, 1);
+			spin_unlock_irqrestore(&priv->lock, flags);
+		}
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ioctl_ops at91sam9x5_video_ioctl_ops = {
+	.vidioc_querycap = at91sam9x5_video_vidioc_querycap,
+	.vidioc_g_fmt_vid_out = at91sam9x5_video_vidioc_g_fmt_vid_out,
+	.vidioc_s_fmt_vid_out = at91sam9x5_video_vidioc_s_fmt_vid_out,
+	.vidioc_g_fmt_vid_overlay = at91sam9x5_video_vidioc_g_fmt_vid_overlay,
+	.vidioc_s_fmt_vid_overlay = at91sam9x5_video_vidioc_s_fmt_vid_overlay,
+	.vidioc_enum_fmt_vid_out = at91sam9x5_video_vidioc_enum_fmt_vid_out,
+	.vidioc_reqbufs = at91sam9x5_video_vidioc_reqbufs,
+	.vidioc_querybuf = at91sam9x5_video_vidioc_querybuf,
+	.vidioc_qbuf = at91sam9x5_video_vidioc_qbuf,
+	.vidioc_dqbuf = at91sam9x5_video_vidioc_dqbuf,
+	.vidioc_streamon = at91sam9x5_video_vidioc_streamon,
+	.vidioc_streamoff = at91sam9x5_video_vidioc_streamoff,
+	.vidioc_queryctrl = at91sam9x5_video_vidioc_queryctrl,
+	.vidioc_g_ctrl = at91sam9x5_video_vidioc_g_ctrl,
+	.vidioc_s_ctrl = at91sam9x5_video_vidioc_s_ctrl,
+};
+
+static int at91sam9x5_video_open(struct file *filp)
+{
+	struct video_device *vdev = video_devdata(filp);
+
+	/*
+	 * XXX: allow only one open? Or is that already enforced by the
+	 * framework?
+	 */
+	filp->private_data = vdev;
+
+	return 0;
+}
+
+static int at91sam9x5_video_release(struct file *filp)
+{
+	struct video_device *vdev = video_devdata(filp);
+
+	dev_dbg(&vdev->dev, "%s\n", __func__);
+
+	return 0;
+}
+
+static int at91sam9x5_video_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct at91sam9x5_video_priv *priv = video_get_drvdata(vdev);
+
+	dev_dbg(&vdev->dev, "%s\n", __func__);
+
+	/* returning -EIO here makes gst-launch segfault */
+	return vb2_mmap(&priv->queue, vma);
+}
+
+static struct v4l2_file_operations at91sam9x5_video_fops = {
+	.owner = THIS_MODULE,
+	.open = at91sam9x5_video_open,
+	.release = at91sam9x5_video_release,
+	.ioctl = video_ioctl2,
+	.mmap = at91sam9x5_video_mmap,
+};
+
+static int at91sam9x5_video_register(struct at91sam9x5_video_priv *priv,
+		struct fb_info *fbinfo)
+{
+	int ret = -ENOMEM;
+	struct platform_device *pdev = priv->pdev;
+	struct resource *res;
+	const struct at91sam9x5_video_pdata *pdata =
+		dev_get_platdata(&pdev->dev);
+	struct vb2_queue *q = &priv->queue;
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	if (priv->fbinfo) {
+		spin_unlock_irqrestore(&priv->lock, flags);
+		return -EBUSY;
+	}
+	priv->fbinfo = fbinfo;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	/* XXX: this doesn't belong here, does it? */
+	pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "failed to get platform data\n");
+		goto err_get_pdata;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "failed to get register base\n");
+		goto err_get_regbase;
+	}
+
+	priv->regbase = ioremap(res->start, resource_size(res));
+	if (!priv->regbase) {
+		dev_err(&pdev->dev, "failed to remap register base\n");
+		goto err_ioremap;
+	}
+
+	/*
+	 * XXX: video_device_alloc is just a kzalloc, so embedding struct
+	 * video_device into struct at91sam9x5_video_priv would work, too.
+	 * Is that allowed?
+	 */
+	priv->video_dev = video_device_alloc();
+	if (!priv->video_dev) {
+		dev_err(&pdev->dev, "failed to alloc video device for %p\n",
+				fbinfo);
+		goto err_video_device_alloc;
+	}
+
+	priv->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev);
+	if (IS_ERR(priv->alloc_ctx)) {
+		ret = PTR_ERR(priv->alloc_ctx);
+		dev_err(&pdev->dev, "failed to init alloc_ctx (%d)\n", ret);
+		goto err_init_ctx;
+	}
+
+	q->ops = &at91sam9x5_video_vb_ops;
+	q->mem_ops = &vb2_dma_contig_memops;
+	q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_WRITE;
+
+	ret = vb2_queue_init(q);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to init queue (%d)\n", ret);
+		goto err_queue_init;
+	}
+
+	priv->video_dev->fops = &at91sam9x5_video_fops;
+	priv->video_dev->ioctl_ops = &at91sam9x5_video_ioctl_ops;
+	priv->video_dev->release = video_device_release;
+
+	video_set_drvdata(priv->video_dev, priv);
+
+	/* reset channel and clear status */
+	at91sam9x5_video_write32(priv, REG_HEOCHDR, REG_HEOCHDR_CHRST);
+	(void)at91sam9x5_video_read32(priv, REG_HEOISR);
+
+	/* set maximal bursting */
+	at91sam9x5_video_write32(priv, REG_HEOCFG0,
+			REG_HEOCFG0_BLEN_INCR16 |
+			REG_HEOCFG0_BLENUV_INCR16);
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret <= 0) {
+		dev_err(&pdev->dev, "failed to get irq from resources (%d)\n",
+				ret);
+		if (!ret)
+			ret = -ENXIO;
+		goto err_get_irq;
+	}
+	priv->irq = ret;
+
+	ret = request_irq(priv->irq, at91sam9x5_video_irq, IRQF_SHARED,
+			DRIVER_NAME, priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request irq (%d)\n", ret);
+		goto err_request_irq;
+	}
+
+	ret = video_register_device(priv->video_dev,
+			/* XXX: really grabber? */ VFL_TYPE_GRABBER, -1);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register video device (%d)\n",
+				ret);
+
+		free_irq(priv->irq, priv);
+ err_request_irq:
+ err_get_irq:
+
+		vb2_queue_release(q);
+err_queue_init:
+
+		vb2_dma_contig_cleanup_ctx(priv->alloc_ctx);
+ err_init_ctx:
+
+		video_device_release(priv->video_dev);
+ err_video_device_alloc:
+
+		iounmap(priv->regbase);
+
+		priv->fbinfo = NULL;
+	}
+ err_ioremap:
+ err_get_regbase:
+ err_get_pdata:
+
+	return ret;
+}
+
+static void at91sam9x5_video_unregister(struct at91sam9x5_video_priv *priv)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (!priv->fbinfo) {
+		spin_unlock_irqrestore(&priv->lock, flags);
+		return;
+	}
+	/* XXX: handle fbinfo being NULL in various callbacks */
+	priv->fbinfo = NULL;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	/* silence DMA */
+	at91sam9x5_video_write32(priv, REG_HEOIDR,
+			REG_HEOIxR_ADD | REG_HEOIxR_DMA | REG_HEOIxR_UADD |
+			REG_HEOIxR_UDMA | REG_HEOIxR_VADD | REG_HEOIxR_VDMA);
+
+	video_unregister_device(priv->video_dev);
+	free_irq(priv->irq, priv);
+	vb2_queue_release(&priv->queue);
+	vb2_dma_contig_cleanup_ctx(priv->alloc_ctx);
+	video_device_release(priv->video_dev);
+	iounmap(priv->regbase);
+}
+
+static int at91sam9x5_video_fb_event_notify(struct notifier_block *self,
+		unsigned long action, void *data)
+{
+	struct at91sam9x5_video_priv *priv =
+		container_of(self, struct at91sam9x5_video_priv, fb_notifier);
+	struct fb_event *event = data;
+	struct fb_info *fbinfo = event->info;
+
+	/* XXX: only do this for atmel_lcdfb devices! */
+	switch (action) {
+	case FB_EVENT_FB_REGISTERED:
+		at91sam9x5_video_register(priv, fbinfo);
+		break;
+
+	case FB_EVENT_FB_UNREGISTERED:
+		at91sam9x5_video_unregister(priv);
+		break;
+	}
+	return 0;
+}
+
+static int __devinit at91sam9x5_video_probe(struct platform_device *pdev)
+{
+	int ret = -ENOMEM;
+	size_t i;
+	struct at91sam9x5_video_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+
+	if (!priv) {
+		dev_err(&pdev->dev, "failed to allocate driver private data\n");
+		goto err_alloc_priv;
+	}
+
+	priv->pdev = pdev;
+	priv->fb_notifier.notifier_call = at91sam9x5_video_fb_event_notify;
+
+	platform_set_drvdata(pdev, priv);
+
+	spin_lock_init(&priv->lock);
+
+	ret = fb_register_client(&priv->fb_notifier);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register fb client (%d)\n", ret);
+
+		kfree(priv);
+err_alloc_priv:
+
+		return ret;
+	}
+
+	/* XXX: This is racy. If a new fb is registered then
+	 * at91sam9x5_video_register is called twice. This should be solved
+	 * somewhere in drivers/fb. priv->fbinfo is used to prevent multiple
+	 * registration.
+	 */
+
+	for (i = 0; i < ARRAY_SIZE(registered_fb); ++i)
+		if (registered_fb[i])
+			at91sam9x5_video_register(priv, registered_fb[i]);
+
+	return 0;
+}
+
+int __devexit at91sam9x5_video_remove(struct platform_device *pdev)
+{
+	struct at91sam9x5_video_priv *priv = platform_get_drvdata(pdev);
+
+	fb_unregister_client(&priv->fb_notifier);
+	at91sam9x5_video_unregister(priv);
+	kfree(priv);
+
+	return 0;
+}
+
+static struct platform_driver at91sam9x5_video_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = at91sam9x5_video_probe,
+	.remove = at91sam9x5_video_remove,
+};
+
+static struct platform_device *at91sam9x5_video_device;
+static int __init at91sam9x5_video_init(void)
+{
+	/* XXX: register the device in arch/arm/mach-at91 */
+	int ret;
+	const struct resource res[] = {
+		{
+			.start = 0xf8038280,
+			.end = 0xf803833f,
+			.flags = IORESOURCE_MEM,
+		}, {
+			.start = 25,
+			.end = 25,
+			.flags = IORESOURCE_IRQ,
+		},
+	};
+	const struct at91sam9x5_video_pdata pdata = {
+		.base_width = 800,
+		.base_height = 480,
+	};
+
+	ret = platform_driver_register(&at91sam9x5_video_driver);
+	if (ret) {
+		pr_err("failed to register driver (%d)", ret);
+		goto err_driver_register;
+	}
+
+	at91sam9x5_video_device = platform_device_register_resndata(NULL,
+			DRIVER_NAME, -1,
+			res, ARRAY_SIZE(res), &pdata, sizeof(pdata));
+	if (IS_ERR(at91sam9x5_video_device)) {
+		ret = PTR_ERR(at91sam9x5_video_device);
+		pr_err("failed to register device (%d)", ret);
+		platform_driver_unregister(&at91sam9x5_video_driver);
+	}
+
+ err_driver_register:
+	return ret;
+}
+module_init(at91sam9x5_video_init);
+
+static void __exit at91sam9x5_video_exit(void)
+{
+	platform_device_unregister(at91sam9x5_video_device);
+	platform_driver_unregister(&at91sam9x5_video_driver);
+}
+module_exit(at91sam9x5_video_exit);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@xxxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
-- 
1.7.5.4

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux