All of lore.kernel.org
 help / color / mirror / Atom feed
* [patch 1/5] s6000 data port driver
@ 2009-03-26 14:36 Daniel Glöckner
  2009-03-26 14:36 ` [patch 2/5] s6000 data port: custom video mode support Daniel Glöckner
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Chris Zankel, linux-media, Oskar Schirmer, Fabian Godehardt,
	Johannes Weiner, Daniel Glöckner

From: Oskar Schirmer <os@emlix.com>

Support for the s6000 on-chip video input/output engine.
Depending on external wiring it supports up to four video devices.

Signed-off-by: Fabian Godehardt <fg@emlix.com>
Signed-off-by: Oskar Schirmer <os@emlix.com>
Signed-off-by: Johannes Weiner <jw@emlix.com>
Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
 drivers/media/video/Kconfig       |    2 +
 drivers/media/video/Makefile      |    2 +
 drivers/media/video/s6dp/Kconfig  |    6 +
 drivers/media/video/s6dp/Makefile |    1 +
 drivers/media/video/s6dp/s6dp.c   | 1664 +++++++++++++++++++++++++++++++++++++
 drivers/media/video/s6dp/s6dp.h   |  121 +++
 include/media/s6dp-link.h         |   63 ++
 7 files changed, 1859 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/video/s6dp/Kconfig
 create mode 100644 drivers/media/video/s6dp/Makefile
 create mode 100644 drivers/media/video/s6dp/s6dp.c
 create mode 100644 drivers/media/video/s6dp/s6dp.h
 create mode 100644 include/media/s6dp-link.h

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index 19cf3b8..a94c20f 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -683,6 +683,8 @@ source "drivers/media/video/ivtv/Kconfig"
 
 source "drivers/media/video/cx18/Kconfig"
 
+source "drivers/media/video/s6dp/Kconfig"
+
 config VIDEO_M32R_AR
 	tristate "AR devices"
 	depends on M32R && VIDEO_V4L1
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index 72f6d03..7109cfe 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -134,6 +134,8 @@ obj-$(CONFIG_VIDEO_CX18) += cx18/
 obj-$(CONFIG_VIDEO_VIVI) += vivi.o
 obj-$(CONFIG_VIDEO_CX23885) += cx23885/
 
+obj-$(CONFIG_VIDEO_S6000) += s6dp/
+
 obj-$(CONFIG_VIDEO_PXA27x)	+= pxa_camera.o
 obj-$(CONFIG_VIDEO_SH_MOBILE_CEU)	+= sh_mobile_ceu_camera.o
 obj-$(CONFIG_VIDEO_OMAP2)		+= omap2cam.o
diff --git a/drivers/media/video/s6dp/Kconfig b/drivers/media/video/s6dp/Kconfig
new file mode 100644
index 0000000..11cc91d
--- /dev/null
+++ b/drivers/media/video/s6dp/Kconfig
@@ -0,0 +1,6 @@
+config VIDEO_S6000
+	tristate "S6000 video"
+	depends on VIDEO_V4L2
+	default n
+	help
+	  Enables the s6000 video driver.
diff --git a/drivers/media/video/s6dp/Makefile b/drivers/media/video/s6dp/Makefile
new file mode 100644
index 0000000..c503d5b
--- /dev/null
+++ b/drivers/media/video/s6dp/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_S6000) += s6dp.o
diff --git a/drivers/media/video/s6dp/s6dp.c b/drivers/media/video/s6dp/s6dp.c
new file mode 100644
index 0000000..434cec5
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp.c
@@ -0,0 +1,1664 @@
+/*
+ * Video driver for S6105 on chip data port device
+ * (c)2007 Stretch, Inc.
+ * (c)2009 emlix GmbH
+ * Authors:	Fabian Godehardt <fg@emlix.com>
+ *		Oskar Schirmer <os@emlix.com>
+ *		Johannes Weiner <jw@emlix.com>
+ *		Daniel Gloeckner <dg@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/dma-mapping.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/s6dp-link.h>
+#include <linux/io.h>
+#include <variant/dmac.h>
+#include <variant/hardware.h>
+#include "s6dp.h"
+
+#define DRV_NAME "s6dp"
+#define DRIVER_VERSION_NUM KERNEL_VERSION(0, 0, 1)
+#define DRV_ERR KERN_ERR DRV_NAME ": "
+#define DRV_INFO KERN_INFO DRV_NAME ": "
+
+#define DP_NB_PORTS	(S6_DPDMA_NB / S6_DP_CHAN_PER_PORT)
+
+/* device not opened */
+#define DP_STATE_UNUSED	0
+/* after open */
+#define DP_STATE_IDLE	1
+/* after reqbufs */
+#define DP_STATE_READY	2
+/* after streamon */
+#define DP_STATE_ACTIVE	3
+
+#define DP_CB_OFFSET	0
+#define DP_Y_OFFSET	1
+#define DP_CR_OFFSET	2
+#define DP_K_OFFSET	3
+
+#define CURRENT_BUF_TYPE(pd) ((pd)->ext.egress ? V4L2_BUF_TYPE_VIDEO_OUTPUT \
+					       : V4L2_BUF_TYPE_VIDEO_CAPTURE)
+
+struct s6dp_frame {
+	void *data;
+	dma_addr_t dma_handle;
+	struct timeval timestamp;
+	struct list_head list;
+	u32 sequence;
+	u32 flags;
+};
+
+struct s6dp {
+	u8 port;
+	u16 irq;
+	void __iomem *dp;
+	u32 dataram;
+	u32 dmac;
+	struct s6dp_link *link;
+	struct list_head idleq;
+	struct list_head busyq;
+	struct list_head fullq;
+	spinlock_t lock;
+	wait_queue_head_t wait;
+	u32 outstanding;
+	struct {
+		u8 state;
+		u8 aligned:1;
+		u8 framerepeat:1;
+		u8 progressive:1;
+
+		u16 width;
+		u16 height;
+		u8 portsperstream;
+		u8 greyperchroma;
+		u16 pixel_total;
+		u8 pixel_offset;
+		u8 pixel_padding;
+		u16 line_total;
+		u16 line_odd_total;
+		u16 line_odd_offset;
+		u16 line_even_offset;
+		u16 odd_vsync_len;
+		u16 odd_vsync_offset;
+		u16 even_vsync_len;
+		u16 even_vsync_offset;
+		u8 odd_hsync_len;
+		u8 odd_hsync_offset;
+		u8 even_hsync_len;
+		u8 even_hsync_offset;
+
+		u32 fourcc;
+		u32 bufsize;
+		u32 chanoff[S6_DP_CHAN_PER_PORT];
+		u32 chansiz[S6_DP_CHAN_PER_PORT];
+		u32 sequence;
+		enum v4l2_field vfield;
+		enum v4l2_colorspace colorspace;
+		v4l2_std_id std_id;
+	} cur;
+	struct s6dp_frame *frames;
+	unsigned nrframes;
+	unsigned nrmapped;
+	struct {
+		u8 is_10bit:1;
+		u8 micron:1;
+		u8 egress:1;
+		u8 use_1120_line_and_crc:1;
+		u8 ext_framing:1;
+		u8 vsync_pol:1;
+		u8 hsync_pol:1;
+		u8 blank_pol:1;
+		u8 field_ctrl:1;
+		u8 blank_ctrl:1;
+		u8 relaxed_framing_mode:1;
+		u32 desc_size;
+	} ext;
+	unsigned int num_io;
+};
+
+static int s6v4l_enumstd(struct s6dp *, struct v4l2_standard *);
+static int s6v4l_s_output(struct file *, void *,  unsigned int);
+static int s6v4l_s_input(struct file *, void *,  unsigned int);
+static int s6v4l_streamoff(struct file *, void *, enum v4l2_buf_type);
+
+#define DP_REG_R(pd, n) readl((pd)->dp + (n))
+#define DP_REG_W(pd, n, v) writel((v), (pd)->dp + (n))
+#define DP_PREG_R(pd, n) readl((pd)->dp + S6_DP_CFG_BASE((pd)->port) + (n))
+#define DP_PREG_W(pd, n, v) writel((v), \
+				   (pd)->dp + S6_DP_CFG_BASE((pd)->port) + (n))
+
+static void s6dp_try_fill_dma(struct s6dp *pd)
+{
+	struct list_head *inq;
+	if (pd->cur.state != DP_STATE_ACTIVE)
+		return;
+	inq = &pd->idleq;
+	while (!list_empty(inq)) {
+		unsigned chan = pd->port * S6_DP_CHAN_PER_PORT;
+		int i;
+		struct s6dp_frame *f;
+		for (i = 0; i < S6_DP_CHAN_PER_PORT; i++)
+			if (pd->cur.chansiz[i]
+			     && s6dmac_fifo_full(pd->dmac, chan + i))
+				return;
+		f = list_first_entry(inq, struct s6dp_frame, list);
+		list_del(&f->list);
+		list_add_tail(&f->list, &pd->busyq);
+		do if (pd->cur.chansiz[--i]) {
+			u32 h, b, s, d;
+				b = (u32)f->dma_handle;
+			b += pd->cur.chanoff[i];
+			h = pd->dataram + S6_DP_CHAN_OFFSET(i);
+			if (pd->ext.egress) {
+				s = b;
+				d = h;
+			} else {
+				s = h;
+				d = b;
+			}
+			s6dmac_put_fifo(pd->dmac, chan + i, s, d,
+				pd->cur.chansiz[i]);
+		} while (i > 0); /* chan #0 must be last to push */
+		pd->outstanding++;
+	}
+}
+
+static void s6dp_err_interrupt(struct s6dp *pd)
+{
+	u32 m, r = DP_REG_R(pd, S6_DP_INT_UNMAP_RAW1);
+	m = 1 << S6_DP_INT_UNDEROVERRUN(pd->port);
+	if (r & m) {
+		printk(DRV_ERR "got overrun/underrun on lane %d\n", pd->port);
+		/* mask this interrupt source */
+		DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE)
+					       & ~m);
+	}
+	m = 1;
+	switch (pd->port) {
+	case 0:
+		m <<= S6_DP_INT_UNMAP_RAW1_DP0_BT1120ERR
+			- S6_DP_INT_UNMAP_RAW1_DP2_BT1120ERR;
+	case 2:
+		m <<= S6_DP_INT_UNMAP_RAW1_DP2_BT1120ERR;
+		if (r & m)
+			printk(DRV_ERR "BT-1120 error bad CRC or line number"
+				" on lane %d\n", pd->port);
+	}
+	m = 1 << S6_DP_INT_WRONGPIXEL(pd->port);
+	if (r & m) {
+		printk(DRV_ERR "bad pixels in lines\n");
+		DP_REG_W(pd, S6_DP_INT_CLEAR, m);
+	}
+	m = 1 << S6_DP_INT_WRONGLINES(pd->port);
+	if (r & m) {
+		printk(DRV_ERR "bad lines in frame\n");
+		DP_REG_W(pd, S6_DP_INT_CLEAR, m);
+	}
+	DP_REG_W(pd, S6_DP_INT_CLEAR, 1 << S6_DP_INT_ERR_INT);
+}
+
+static void s6dp_tc_interrupt(struct s6dp *pd)
+{
+	unsigned c = S6_DP_CHAN_PER_PORT * (pd->port + 1);
+	unsigned i = S6_DP_CHAN_PER_PORT;
+	u8 event[S6_DP_CHAN_PER_PORT];
+	do {
+		c -= 1;
+		s6dmac_lowwmark_irq(pd->dmac, c);
+		s6dmac_pendcnt_irq(pd->dmac, c);
+		event[--i] = s6dmac_termcnt_irq(pd->dmac, c);
+	} while (i > 0);
+	while (!pd->cur.chansiz[i]) {
+		if (++i >= S6_DP_CHAN_PER_PORT)
+			return;
+		c += 1;
+	}
+	if (event[i]) {
+		struct timeval now;
+		u32 newfc, pending, global;
+		struct list_head *outq = &pd->fullq;
+
+		do_gettimeofday(&now);
+		global = readl(S6_REG_GREG1 + S6_GREG1_GLOBAL_TIMER);
+		DP_REG_W(pd, S6_DP_INT_CLEAR, 1 << S6_DP_INT_TERMCNT(pd->port));
+		newfc = DP_PREG_R(pd, S6_DP_FRAME_COUNT);
+		pending = s6dmac_pending_count(pd->dmac, c);
+		if (unlikely(!pending) && pd->cur.framerepeat)
+			pending = 1;
+		while (pd->outstanding > pending) {
+			struct s6dp_frame *f;
+			u32 delta;
+			delta = global - s6dmac_timestamp(pd->dmac, c);
+			if (unlikely(list_empty(&pd->busyq))) {
+				/* shouldn't happen */
+				printk(DRV_ERR "no buffers in interrupt\n");
+				break;
+			}
+			f = list_first_entry(&pd->busyq, struct s6dp_frame,
+					     list);
+			f->sequence = pd->cur.sequence++;
+			f->flags &= ~V4L2_BUF_FLAG_QUEUED;
+			f->flags |= V4L2_BUF_FLAG_DONE;
+			f->timestamp = now;
+			f->timestamp.tv_usec -= delta * 10;
+			while (f->timestamp.tv_usec < 0) {
+				f->timestamp.tv_sec -= 1;
+				f->timestamp.tv_usec += 1000000;
+			}
+
+			list_del(&f->list);
+			list_add_tail(&f->list, outq);
+			pd->outstanding--;
+		}
+		pd->cur.sequence = newfc;
+		if (unlikely(list_empty(&pd->busyq)) && pending)
+			printk(DRV_ERR "no repeating frame?\n");
+		s6dp_try_fill_dma(pd);
+		if (!list_empty(&pd->fullq))
+			wake_up_interruptible(&pd->wait);
+	}
+}
+
+static irqreturn_t s6dp_interrupt(int irq, void *dev_id)
+{
+	struct video_device **devs = dev_id;
+	irqreturn_t ret = IRQ_NONE;
+	int i;
+	for (i = 0; i < DP_NB_PORTS; i++) {
+		struct video_device *dev = devs[i];
+		if (dev) {
+			struct s6dp *pd = video_get_drvdata(dev);
+			u32 s;
+			spin_lock(&pd->lock);
+			s = DP_REG_R(pd, S6_DP_INT_STATUS);
+			if (unlikely(s & (1 << S6_DP_INT_ERR_INT))) {
+				s6dp_err_interrupt(pd);
+				ret = IRQ_HANDLED;
+			}
+			if (s & (1 << S6_DP_INT_TERMCNT(pd->port))) {
+				s6dp_tc_interrupt(pd);
+				ret = IRQ_HANDLED;
+			}
+			spin_unlock(&pd->lock);
+		}
+	}
+	return ret;
+}
+
+static int s6dp_dma_init(struct video_device *dev)
+{
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned i, n, burstsize;
+
+	n = DP_PREG_R(pd, S6_DP_CBCR_DMA_CONVERT) |
+	    DP_PREG_R(pd, S6_DP_Y_DMA_CONVERT) |
+	    DP_PREG_R(pd, S6_DP_ANC_DMA_CONVERT);
+	burstsize = 7;
+	for (i = (1 << (burstsize - 4)) - 1; n & i; i >>= 1)
+		burstsize--;
+
+	n = 3;
+	i = 0;
+	do {
+		int ret;
+		ret = s6dmac_request_chan(pd->dmac,
+			pd->port * S6_DP_CHAN_PER_PORT + i, /* channel */
+			0,				    /* prio */
+			0,				    /* pxfer */
+			pd->ext.egress,			    /* srcinc */
+			!pd->ext.egress,		    /* dstinc */
+			0,				    /* sc./ga. */
+			0,				    /* srcskip */
+			0,				    /* dstskip */
+			burstsize,			    /* burstsize */
+			-1,				    /* bwconsv */
+			pd->ext.egress ? 4 : 2,		    /* lowwmark */
+			1,				    /* timestamp */
+			0);				    /* enable */
+		if (ret < 0) {
+			printk(DRV_ERR
+				"error - can not request DMA channel %d\n",
+				pd->port * S6_DP_CHAN_PER_PORT + i);
+			goto errdma;
+		}
+	} while (++i < n);
+
+	pd->cur.framerepeat = 1;
+	s6dmac_dp_setup_group(pd->dmac, pd->port, n, 1);
+
+	DP_REG_W(pd, S6_DP_VIDEO_DMA_CFG, (DP_REG_R(pd, S6_DP_VIDEO_DMA_CFG)
+		& ~(7 << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)))
+		| (burstsize << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)));
+
+	return 0;
+errdma:
+	while (i > 0) {
+		i -= 1;
+		s6dmac_release_chan(pd->dmac,
+				    pd->port * S6_DP_CHAN_PER_PORT + i);
+	}
+	return -EIO;
+}
+
+static void s6dp_dma_free(struct video_device *dev)
+{
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned i, n;
+
+	if (pd->cur.state < DP_STATE_ACTIVE)
+		return;
+
+	n = 3;
+	i = 0;
+	do {
+		s6dmac_release_chan(pd->dmac,
+				    pd->port * S6_DP_CHAN_PER_PORT + i);
+	} while (++i < n);
+}
+
+static int s6dp_setup_stream(struct video_device *dev)
+{
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned i, n, y;
+	unsigned long flags;
+
+	i = pd->cur.portsperstream;
+	if (i != 1) {
+		printk(DRV_ERR "multi port mode not implemented\n");
+		/* needs cross device checking for free channels */
+		return -EINVAL;
+	}
+	pd->cur.portsperstream = i; /* FIXME -> set_current */
+	/* write port configuration (24 regs. minus ANC stuff, see below) */
+	DP_PREG_W(pd, S6_DP_PIXEL_TOTAL, pd->cur.pixel_total);
+	DP_PREG_W(pd, S6_DP_PIXEL_ACTIVE,
+		  pd->cur.width / pd->cur.greyperchroma);
+	DP_PREG_W(pd, S6_DP_PIXEL_OFFSET, pd->cur.pixel_offset);
+	DP_PREG_W(pd, S6_DP_PIXEL_PADDING, pd->cur.pixel_padding);
+	DP_PREG_W(pd, S6_DP_LINE_TOTAL, pd->cur.line_total);
+	DP_PREG_W(pd, S6_DP_LINE_ODD_TOTAL, pd->cur.line_odd_total);
+	i = pd->cur.progressive ? 0 : pd->cur.height/2;
+	DP_PREG_W(pd, S6_DP_LINE_ODD_ACTIVE, pd->cur.height - i);
+	DP_PREG_W(pd, S6_DP_LINE_ODD_OFFSET, pd->cur.line_odd_offset);
+	DP_PREG_W(pd, S6_DP_LINE_EVEN_ACTIVE, i);
+	DP_PREG_W(pd, S6_DP_LINE_EVEN_OFFSET, pd->cur.line_even_offset);
+	DP_PREG_W(pd, S6_DP_ODD_VSYNC_LENGTH, pd->cur.odd_vsync_len);
+	DP_PREG_W(pd, S6_DP_ODD_VSYNC_OFFSET, pd->cur.odd_vsync_offset);
+	DP_PREG_W(pd, S6_DP_EVEN_VSYNC_LENGTH, pd->cur.even_vsync_len);
+	DP_PREG_W(pd, S6_DP_EVEN_VSYNC_OFFSET, pd->cur.even_vsync_offset);
+	DP_PREG_W(pd, S6_DP_ODD_HSYNC_LENGTH, pd->cur.odd_hsync_len);
+	DP_PREG_W(pd, S6_DP_ODD_HSYNC_OFFSET, pd->cur.odd_hsync_offset);
+	DP_PREG_W(pd, S6_DP_EVEN_HSYNC_LENGTH, pd->cur.even_hsync_len);
+	DP_PREG_W(pd, S6_DP_EVEN_HSYNC_OFFSET, pd->cur.even_hsync_offset);
+
+	DP_PREG_W(pd, S6_DP_ANC_PIXEL_ACTIVE, 0);
+	DP_PREG_W(pd, S6_DP_ANC_PIXEL_OFFSET, 0);
+	DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_ACTIVE, 0);
+	DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_OFFSET, 0);
+	DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_ACTIVE, 0);
+	DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_OFFSET, 0);
+
+	/*
+	 * Program the _dma_convert registers.  These values calculate for the
+	 * hardware the number of bursts a frame will require (in video mode).
+	 * In streaming mode, cbcr_dma_convert indicates the number of 16b
+	 * lines to do before issuing the last transfer.
+	 */
+	n = pd->cur.width / pd->cur.greyperchroma;
+	y = pd->cur.height;
+	i = pd->ext.is_10bit ? 12 : 16;
+	DP_PREG_W(pd, S6_DP_CBCR_DMA_CONVERT, ((n + i - 1) / i) * y);
+	i /= pd->cur.greyperchroma;
+	DP_PREG_W(pd, S6_DP_Y_DMA_CONVERT, ((n + i - 1) / i) * y);
+	DP_PREG_W(pd, S6_DP_ANC_DMA_CONVERT, 0);
+
+	/* Program dp_config. Function of mode and optional configs */
+	/* Video configuration */
+	i = (pd->cur.greyperchroma == 1 ? S6_DP_VIDEO_CFG_MODE_444_SERIAL
+					: S6_DP_VIDEO_CFG_MODE_422_SERIAL)
+						<< S6_DP_VIDEO_CFG_MODE;
+	i |= pd->ext.use_1120_line_and_crc << S6_DP_VIDEO_CFG_1120_VIDEO_MODE;
+	/* Progressive / interlaced */
+	/* Micron mode: Must be progressive (regardless of what mode says) */
+	i |= pd->ext.micron << S6_DP_VIDEO_CFG_MICRON_MODE;
+	i |= (pd->ext.micron | pd->cur.progressive)
+			<< S6_DP_VIDEO_CFG_INTERL_OR_PROGR;
+	/* External framing */
+	if (pd->ext.ext_framing) {
+		i |= 1 << S6_DP_VIDEO_CFG_FRAMING;
+		i |= pd->ext.vsync_pol << S6_DP_VIDEO_CFG_VSYNC_POL;
+		i |= pd->ext.hsync_pol << S6_DP_VIDEO_CFG_HSYNC_POL;
+		i |= pd->ext.blank_pol << S6_DP_VIDEO_CFG_BLANK_POL;
+		i |= pd->ext.field_ctrl << S6_DP_VIDEO_CFG_FIELD_CTRL;
+		i |= pd->ext.blank_ctrl << S6_DP_VIDEO_CFG_BLANK_CTRL;
+	} else {
+		/* Embedded framing */
+		i |= pd->ext.relaxed_framing_mode << S6_DP_VIDEO_CFG_RELAX_MODE;
+	}
+	i |= pd->ext.is_10bit << S6_DP_VIDEO_CFG_8_OR_10;
+	i |= pd->ext.egress << S6_DP_VIDEO_CFG_IN_OR_OUT;
+	DP_REG_W(pd, S6_DP_VIDEO_CFG(pd->port), i);
+
+	spin_lock_irqsave(&pd->lock, flags);
+	/*
+	 * Program the clk_mux in DP_CLK_SETTING.
+	 * NOTE: all ports share this register, so this would need to be
+	 * atomic if this function were re-entrant (which it is not).
+	 */
+	i = DP_REG_R(pd, S6_DP_DP_CLK_SETTING)
+		& ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port));
+	i |= (pd->ext.egress ? 0 : 1) <<
+		S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+	DP_REG_W(pd, S6_DP_DP_CLK_SETTING, i);
+
+	/* Initialize DP DMA registers for this stream */
+	i = s6dp_dma_init(dev);
+	spin_unlock_irqrestore(&pd->lock, flags);
+	return i;
+}
+
+static void _s6dp_reset_port(struct s6dp *pd)
+{
+	unsigned i, m;
+	unsigned long flags;
+	struct list_head *queue[3] = {
+		&pd->idleq, &pd->busyq, &pd->fullq
+	};
+
+	spin_lock_irqsave(&pd->lock, flags);
+	DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE)
+		& ~((1 << S6_DP_INT_UNDEROVERRUN(pd->port))
+		  | (1 << S6_DP_INT_WRONGPIXEL(pd->port))
+		  | (1 << S6_DP_INT_WRONGLINES(pd->port))));
+
+	/* Clear the enable bit for the entire DMA group */
+	s6dmac_dp_switch_group(pd->dmac, pd->port, 0);
+	pd->outstanding = 0;
+	spin_unlock_irqrestore(&pd->lock, flags);
+	/* wait for first channel's DMA to become disabled */
+	i = 0;
+	do if (pd->cur.chansiz[i]) {
+		while (s6dmac_channel_enabled(pd->dmac,
+				i + pd->port * S6_DP_CHAN_PER_PORT))
+			;
+		break;
+	} while (++i < S6_DP_CHAN_PER_PORT);
+
+	spin_lock_irqsave(&pd->lock, flags);
+	DP_REG_W(pd, S6_DP_VIDEO_ENABLE, DP_REG_R(pd, S6_DP_VIDEO_ENABLE)
+				 & ~(1 << S6_DP_VIDEO_ENABLE_ENABLE(pd->port)));
+
+	/* FIXME, sort out true channel list: */
+	s6dmac_disable_error_irqs(pd->dmac,
+		15 << (pd->port * S6_DP_CHAN_PER_PORT));
+
+	DP_REG_W(pd, S6_DP_INT_CLEAR,
+		(1 << S6_DP_INT_LOWWMARK(pd->port)) |
+		(1 << S6_DP_INT_PENDGCNT(pd->port)) |
+		(1 << S6_DP_INT_TERMCNT(pd->port)));
+	for (i = 0; i < 3; i++) {
+		struct s6dp_frame *f, *next;
+		list_for_each_entry_safe(f, next, queue[i], list) {
+			list_del_init(&f->list);
+			f->flags &= ~(V4L2_BUF_FLAG_QUEUED |
+				      V4L2_BUF_FLAG_DONE);
+		}
+	}
+	m = 1 << S6_DP_INT_DMAERR;
+	for (i = 0; i < S6_DP_CHAN_PER_PORT; i++)
+		if (pd->cur.chansiz[i])
+			m |= (1 << (i + S6_DP_CHAN_PER_PORT * pd->port));
+	DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE) & ~m);
+	spin_unlock_irqrestore(&pd->lock, flags);
+}
+
+static void s6dp_reset_port(struct video_device *dev)
+{
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned i;
+	unsigned long flags;
+
+	_s6dp_reset_port(pd);
+	spin_lock_irqsave(&pd->lock, flags);
+	s6dp_dma_free(dev);
+	DP_REG_W(pd, S6_DP_DP_CLK_SETTING, DP_REG_R(pd, S6_DP_DP_CLK_SETTING)
+		& ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK
+			<< S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port)));
+	spin_unlock_irqrestore(&pd->lock, flags);
+	DP_REG_W(pd, S6_DP_VIDEO_CFG(pd->port), 0);
+	for (i = 0; i < 24; i++)
+		DP_PREG_W(pd, i*4, 0);
+}
+
+static int s6dp_video_open(struct file *file)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned long flags;
+
+	file->private_data = dev;
+	spin_lock_irqsave(&pd->lock, flags);
+	if (pd->cur.state != DP_STATE_UNUSED) {
+		spin_unlock_irqrestore(&pd->lock, flags);
+		return -EBUSY; /* TODO: v4l2 allows multiple opens */
+	}
+	pd->cur.state = DP_STATE_IDLE;
+	spin_unlock_irqrestore(&pd->lock, flags);
+
+	/* deferred initialization to avoid problems with the probing order */
+	if (!pd->cur.height) {
+		struct v4l2_cropcap cap;
+		struct v4l2_format vf;
+		v4l2_std_id first = 0;
+		int i;
+
+		if (pd->ext.egress)
+			s6v4l_s_output(file, file->private_data, 0);
+		else
+			s6v4l_s_input(file, file->private_data, 0);
+
+		for (i = 0; ; i++) {
+			struct v4l2_standard std;
+			std.index = i;
+			if (s6v4l_enumstd(pd, &std) < 0)
+				break;
+			if (!first)
+				first = std.id;
+			dev->tvnorms |= std.id;
+		}
+		if (dev->ioctl_ops->vidioc_s_std)
+			if (!dev->ioctl_ops->vidioc_s_std(file,
+							  file->private_data,
+							  &first))
+				dev->current_norm = first;
+		cap.type = CURRENT_BUF_TYPE(pd);
+		if (!dev->ioctl_ops->vidioc_cropcap(file, file->private_data,
+						    &cap)) {
+			struct v4l2_crop vc;
+			vc.type = CURRENT_BUF_TYPE(pd);
+			vc.c = cap.defrect;
+			dev->ioctl_ops->vidioc_s_crop(file, file->private_data,
+						      &vc);
+		}
+		vf.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P;
+		vf.fmt.pix.field = V4L2_FIELD_ANY;
+		vf.fmt.pix.width = 704;
+		vf.fmt.pix.height = 480;
+		vf.fmt.pix.bytesperline = 0;
+		vf.fmt.pix.priv = 0;
+		if (pd->ext.egress)
+			dev->ioctl_ops->vidioc_s_fmt_vid_out(file,
+							     file->private_data,
+							     &vf);
+		else
+			dev->ioctl_ops->vidioc_s_fmt_vid_cap(file,
+							     file->private_data,
+							     &vf);
+	}
+	return 0;
+}
+
+static void s6dp_relbufs(struct video_device *dev)
+{
+	struct s6dp *pd = video_get_drvdata(dev);
+	int i;
+	unsigned long flags;
+
+	if (!pd->nrframes)
+		return;
+	spin_lock_irqsave(&pd->lock, flags);
+	INIT_LIST_HEAD(&pd->idleq);
+	INIT_LIST_HEAD(&pd->busyq);
+	INIT_LIST_HEAD(&pd->fullq);
+	spin_unlock_irqrestore(&pd->lock, flags);
+	for (i = 0; i < pd->nrframes; i++)
+		dma_free_coherent(dev->parent, pd->cur.bufsize,
+				  pd->frames[i].data, pd->frames[i].dma_handle);
+	kfree(pd->frames);
+	pd->nrframes = 0;
+}
+
+static int s6dp_video_close(struct file *file)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	/* reset port and free dma channels */
+	s6dp_reset_port(dev);
+
+	/* free buffer(s) */
+	s6dp_relbufs(dev);
+	pd->cur.state = DP_STATE_UNUSED;
+	return 0;
+}
+
+static void s6dp_video_vm_close(struct vm_area_struct *area)
+{
+	struct video_device *dev = area->vm_file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	struct s6dp_frame *f = area->vm_private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&pd->lock, flags);
+	f->flags &= ~V4L2_BUF_FLAG_MAPPED;
+	pd->nrmapped--;
+	spin_unlock_irqrestore(&pd->lock, flags);
+}
+
+static struct vm_operations_struct s6dp_vm_ops = {
+	.close = s6dp_video_vm_close,
+};
+
+static int s6dp_video_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned long flags;
+	u32 buf;
+	int index;
+
+	/* we use the vma_pgoff to distinguish between the buffers */
+#define MAX_FRAMES 256
+	index = vma->vm_pgoff & 0xFF;
+	if (pd->cur.state < DP_STATE_READY || index >= pd->nrframes)
+		return -ENOMEM;
+	buf = (u32)pd->frames[index].data;
+	BUG_ON(buf & ~PAGE_MASK);
+
+	vma->vm_pgoff = buf >> PAGE_SHIFT;
+	if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+			PAGE_ALIGN(pd->cur.bufsize), vma->vm_page_prot)) {
+		printk(DRV_ERR "error - mapping frame #%d\n", index);
+		return -EAGAIN;
+	}
+	vma->vm_flags &= ~VM_IO;	/* not I/O memory */
+	vma->vm_flags |= VM_MAYSHARE | VM_RESERVED;	/* avoid to swap out */
+	vma->vm_ops = &s6dp_vm_ops;
+	vma->vm_private_data = pd->frames + index;
+
+	spin_lock_irqsave(&pd->lock, flags);
+	pd->nrmapped++;
+	pd->frames[index].flags |= V4L2_BUF_FLAG_MAPPED;
+	spin_unlock_irqrestore(&pd->lock, flags);
+	return 0;
+}
+
+static unsigned long s6dp_video_get_unmapped_area(struct file *file,
+				unsigned long addr, unsigned long len,
+				unsigned long pgoff, unsigned long flags)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	int index;
+
+	index = pgoff & 0xFF;
+	if (pd->cur.state < DP_STATE_READY || index >= pd->nrframes)
+		return -ENOMEM;
+	return (unsigned long)pd->frames[index].data;
+}
+
+static unsigned int s6dp_video_poll(struct file *file, poll_table *wait)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	poll_wait(file, &pd->wait, wait);
+	if (pd->cur.state < DP_STATE_ACTIVE)
+		return POLLERR;
+	if (list_empty(&pd->fullq))
+		return 0;
+	return pd->ext.egress ? (POLLOUT | POLLWRNORM) : (POLLIN | POLLRDNORM);
+}
+
+static long s6dp_video_ioctl(struct file *file, unsigned int cmd,
+			    unsigned long arg)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	if (cmd == VIDIOC_ENUMSTD) {
+		struct v4l2_standard std;
+		int ret;
+		if (copy_from_user(&std, (void __user *)arg, sizeof(std)))
+			return -EFAULT;
+		ret = s6v4l_enumstd(pd, &std);
+		if (copy_to_user((void __user *)arg, &std, sizeof(std)))
+			return -EFAULT;
+		return ret;
+	}
+	return video_ioctl2(file, cmd, arg);
+}
+
+static inline u32 s6dp_byteperline(struct s6dp *pd, int divide)
+{
+	u32 n = pd->cur.width;
+	if (divide)
+		n /= pd->cur.greyperchroma;
+	if (pd->ext.is_10bit) /* pack 3 samples into 4 bytes: */
+		n = ((n * 4) + 2) / 3;
+	return n;
+}
+
+static inline u32 s6dp_bytealigned(u32 unaligned)
+{
+	return (unaligned + 15) & ~15;
+}
+
+static inline u32 s6dp_byteperframe(struct s6dp *pd, int divide, u32 perline)
+{
+	u32 n = pd->cur.height;
+	if (divide)
+		n /= 2;
+	return n * perline;
+}
+
+static inline unsigned s6dp_set_hw2buf(struct s6dp *pd, int chan,
+			unsigned offset, unsigned asize)
+{
+	pd->cur.chanoff[chan] = offset;
+	pd->cur.chansiz[chan] = asize;
+	return 1 << chan;
+}
+
+static int s6dp_set_current(struct video_device *dev, u32 fourcc, int aligned)
+{
+	struct s6dp *pd = video_get_drvdata(dev);
+	u32 uyl, ayl, uyf, ayf, ucl, acl, acf;
+	pd->cur.fourcc = fourcc;
+	pd->cur.aligned = aligned;
+	pd->cur.chansiz[DP_K_OFFSET] = 0;
+	uyl = s6dp_byteperline(pd, 0);
+	ayl = s6dp_bytealigned(uyl);
+	ucl = s6dp_byteperline(pd, 1);
+	acl = s6dp_bytealigned(ucl);
+	uyf = s6dp_byteperframe(pd, 0, uyl);
+	ayf = s6dp_byteperframe(pd, 0, ayl);
+	if (!aligned && ayl != pd->cur.greyperchroma * acl)
+		return -EINVAL;
+	acf = s6dp_byteperframe(pd, 0, acl);
+	switch (fourcc) {
+	case V4L2_PIX_FMT_YUV444P:
+		if (aligned || uyl == ayl) {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+			s6dp_set_hw2buf(pd, DP_CR_OFFSET, ayf + acf, acf);
+			pd->cur.bufsize = ayf + 2 * acf;
+		}
+		break;
+	case V4L2_PIX_FMT_YUV422P:
+		if (aligned || ucl == acl) {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+			s6dp_set_hw2buf(pd, DP_CR_OFFSET, ayf + acf, acf);
+			pd->cur.bufsize = ayf + 2 * acf;
+		}
+		break;
+	default:
+		BUG();
+	}
+	BUG_ON(pd->cur.bufsize >= (1 << 24));
+	return 0;
+}
+
+static int s6v4l_update(struct s6dp *pd, int r)
+{
+	struct s6dp_mode mode;
+	int divi, sub;
+
+	if (r < 0)
+		return r;
+	if (!pd->link || !pd->link->g_mode)
+		return -EINVAL; /* no driver, no V4L */
+	pd->link->g_mode(pd->link->context, &mode);
+
+	pd->cur.width = mode.pixel_active;
+	pd->cur.height = mode.odd_active + mode.even_active;
+	pd->cur.progressive = mode.progressive;
+	switch (mode.type) {
+	case S6_DP_VIDEO_CFG_MODE_422_SERIAL:
+		pd->cur.portsperstream = 1;
+		divi = 2;
+		sub = 2;
+		break;
+	case S6_DP_VIDEO_CFG_MODE_444_SERIAL:
+		pd->cur.portsperstream = 1;
+		divi = 1;
+		sub = 3;
+		break;
+	case S6_DP_VIDEO_CFG_MODE_422_PARALLEL:
+		pd->cur.portsperstream = 2;
+		divi = 2;
+		sub = 4;
+		break;
+	case S6_DP_VIDEO_CFG_MODE_444_PARALLEL:
+		pd->cur.portsperstream = 3;
+		divi = 1;
+		sub = 8;
+		break;
+	default:
+		divi = 1;
+		sub = 0;
+	}
+	pd->cur.greyperchroma = divi;
+	pd->cur.pixel_total = mode.pixel_total / divi - sub;
+	pd->cur.pixel_offset = mode.pixel_offset / divi;
+	pd->cur.pixel_padding = mode.pixel_padding / divi;
+	pd->cur.line_total = mode.framelines;
+	pd->cur.line_odd_total = mode.odd_total;
+	pd->cur.line_odd_offset = mode.odd_first;
+	pd->cur.line_even_offset = mode.even_first;
+	pd->cur.odd_vsync_len = mode.odd_vsync_len;
+	pd->cur.odd_vsync_offset = mode.odd_vsync_offset;
+	pd->cur.even_vsync_len = mode.even_vsync_len;
+	pd->cur.even_vsync_offset = mode.even_vsync_offset;
+	pd->cur.odd_hsync_len = mode.hsync_len / divi;
+	pd->cur.odd_hsync_offset = mode.hsync_offset / divi;
+	pd->cur.even_hsync_len = mode.hsync_len / divi;
+	pd->cur.even_hsync_offset = mode.hsync_offset / divi;
+	pd->ext.ext_framing = !mode.embedded_sync;
+	pd->ext.micron = mode.micron_mode;
+	pd->ext.vsync_pol = mode.vsync_pol;
+	pd->ext.hsync_pol = mode.hsync_pol;
+	pd->ext.blank_pol = mode.blank_pol;
+	pd->ext.field_ctrl = mode.field_ctrl;
+	pd->ext.blank_ctrl = mode.blank_ctrl;
+	pd->ext.relaxed_framing_mode = mode.relaxed_framing;
+	pd->ext.is_10bit = mode.ten_bit;
+	pd->ext.use_1120_line_and_crc = mode.line_and_crc;
+	return 0;
+}
+
+
+static int s6v4l_enumstd(struct s6dp *pd, struct v4l2_standard *std)
+{
+	int ret = -EINVAL;
+	if (pd->link && pd->link->e_std)
+		ret = pd->link->e_std(pd->link->context, std);
+	return ret;
+}
+
+static int s6v4l_s_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	int ret = -EINVAL;
+	if (pd->link && pd->link->s_std)
+		ret = pd->link->s_std(pd->link->context, std,
+				      pd->cur.state >= DP_STATE_READY);
+	return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	u32 i;
+	struct s6dp_frame *f;
+	unsigned long flags;
+
+	if (pd->cur.state < DP_STATE_READY)
+		return -EINVAL;
+	if (p->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+	if (p->type != CURRENT_BUF_TYPE(pd))
+		return -EINVAL;
+	i = p->index;
+	if (i >= pd->nrframes) {
+		printk(DRV_ERR "buffer index range error (%u/%u)\n",
+			i, pd->nrframes);
+		return -EINVAL;
+	}
+	f = &pd->frames[i];
+	if (!list_empty(&f->list)) {
+		printk(DRV_ERR "error - frame %d already queued\n", i);
+		return -EINVAL;
+	}
+	f->timestamp.tv_sec = 0;
+	f->timestamp.tv_usec = 0;
+	f->flags |= V4L2_BUF_FLAG_QUEUED;
+	p->flags = f->flags;
+	spin_lock_irqsave(&pd->lock, flags);
+	list_add_tail(&f->list, &pd->idleq);
+	s6dp_try_fill_dma(pd);
+	spin_unlock_irqrestore(&pd->lock, flags);
+	return 0;
+}
+
+static int s6v4l_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	struct s6dp_frame *f;
+	unsigned long flags;
+
+	if (pd->cur.state < DP_STATE_READY)
+		return -EINVAL;
+retry:
+	if (!(file->f_flags & O_NONBLOCK) &&
+	    wait_event_interruptible(pd->wait, !list_empty(&pd->fullq)))
+		return -ERESTARTSYS;
+	spin_lock_irqsave(&pd->lock, flags);
+	if (list_empty(&pd->fullq)) {
+		spin_unlock_irqrestore(&pd->lock, flags);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		goto retry;
+	}
+	f = list_first_entry(&pd->fullq, struct s6dp_frame, list);
+	list_del_init(&f->list);
+	f->flags &= ~V4L2_BUF_FLAG_DONE;
+	spin_unlock_irqrestore(&pd->lock, flags);
+
+	p->index = f - &pd->frames[0];
+	p->timestamp = f->timestamp;
+	p->sequence = f->sequence;
+	p->memory = V4L2_MEMORY_MMAP;
+	p->flags = f->flags;
+	p->field = pd->cur.vfield;
+	p->length =  pd->cur.bufsize;
+	if (!pd->ext.egress)
+		p->bytesused = pd->cur.bufsize;
+	return 0;
+}
+
+static int s6v4l_reqbufs(struct file *file, void *priv,
+			 struct v4l2_requestbuffers *req)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	int i;
+
+	if (req->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+	if (req->type != CURRENT_BUF_TYPE(pd))
+		return -EINVAL;
+	if (pd->nrmapped)
+		return -EBUSY;
+	if (pd->cur.state > DP_STATE_READY) {
+		if (req->count)
+			return -EBUSY;
+		i = s6v4l_streamoff(file, priv, req->type);
+		if (i < 0)
+			return i;
+	}
+	if (req->count > MAX_FRAMES)
+		req->count = MAX_FRAMES;
+
+	s6dp_relbufs(dev);
+	if (req->count == 0) {
+		pd->cur.state = DP_STATE_IDLE;
+		return 0;
+	}
+
+	pd->frames =
+		kmalloc(req->count * sizeof(struct s6dp_frame), GFP_KERNEL);
+	if (!pd->frames)
+		return -ENOMEM;
+	for (i = 0; i < req->count; i++) {
+		struct s6dp_frame *f;
+		f = &pd->frames[i];
+		f->data = dma_alloc_coherent(dev->parent, pd->cur.bufsize,
+					     &f->dma_handle, GFP_KERNEL);
+		if (!f->data) {
+			req->count = i;
+			break;
+		}
+		INIT_LIST_HEAD(&f->list);
+		f->flags = 0;
+	}
+	if (!i) {
+		kfree(pd->frames);
+		return -ENOMEM;
+	}
+	pd->nrframes = i;
+	pd->cur.state = DP_STATE_READY;
+	return 0;
+}
+
+static int s6v4l_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (p->type != CURRENT_BUF_TYPE(pd))
+		return -EINVAL;
+	if (pd->cur.state < DP_STATE_READY)
+		return -EINVAL;
+	if (p->index >= pd->nrframes)
+		return -EINVAL;
+
+	p->memory = V4L2_MEMORY_MMAP;
+	p->m.offset = p->index << PAGE_SHIFT;	/*
+						 * a "magic cookie" that the
+						 * appl. can pass to mmap to
+						 * specifiy which buffer is
+						 * being mapped
+						 */
+
+	p->length = pd->cur.bufsize;
+	p->flags = pd->frames[p->index].flags;
+	p->field = pd->cur.vfield;
+	return 0;
+}
+
+static int s6v4l_streamon(struct file *file, void *priv,
+			enum v4l2_buf_type vtype)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned i, m;
+	unsigned long flags;
+
+	if (pd->cur.state != DP_STATE_READY) {
+		printk(DRV_ERR "device not ready\n");
+		return -EINVAL;
+	}
+
+	if (list_empty(&pd->idleq)) {
+		printk(DRV_ERR "no buffers queued\n");
+		return -EINVAL;
+	}
+
+	i = s6dp_setup_stream(dev);
+	if (i) {
+		printk(DRV_ERR "error - video setup failed\n");
+		return i;
+	}
+	pd->cur.sequence = 0;
+	pd->cur.state = DP_STATE_ACTIVE;
+
+	/* Set the enable bit for the entire DMA group */
+	s6dmac_dp_switch_group(pd->dmac, pd->port, 1);
+
+	m = (1 << S6_DP_INT_DMAERR)
+		| (1 << S6_DP_INT_UNDEROVERRUN(pd->port))
+		| (1 << S6_DP_INT_WRONGPIXEL(pd->port))
+		| (1 << S6_DP_INT_WRONGLINES(pd->port));
+	for (i = 0; i < S6_DP_CHAN_PER_PORT; i++)
+		if (pd->cur.chansiz[i])
+			m |= (1 << (i + S6_DP_CHAN_PER_PORT * pd->port));
+	spin_lock_irqsave(&pd->lock, flags);
+	DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE) | m);
+	DP_REG_W(pd, S6_DP_VIDEO_ENABLE, DP_REG_R(pd, S6_DP_VIDEO_ENABLE)
+		| (1 << S6_DP_VIDEO_ENABLE_ENABLE(pd->port)));
+	s6dp_try_fill_dma(pd);
+	spin_unlock_irqrestore(&pd->lock, flags);
+	return 0;
+}
+
+
+static int s6v4l_streamoff(struct file *file, void *priv,
+			enum v4l2_buf_type type)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (pd->cur.state != DP_STATE_ACTIVE)
+		return -EINVAL;
+	s6dp_reset_port(dev);
+	pd->cur.state = DP_STATE_READY;
+	return 0;
+}
+
+const static struct {
+	u32 pixelformat;
+	u8 *description;
+} s6dp_enum_fmt[] = {
+	{	V4L2_PIX_FMT_YUV444P,
+		"YUV 4:4:4 planar",
+	},
+	{	V4L2_PIX_FMT_YUV422P,
+		"YUV 4:2:2 planar",
+	},
+};
+
+static int s6v4l_enum_fmt_cap(struct file *file, void *priv,
+			struct v4l2_fmtdesc *f)
+{
+	u32 i = f->index;
+	if (i >= ARRAY_SIZE(s6dp_enum_fmt))
+		return -EINVAL;
+	f->pixelformat = s6dp_enum_fmt[i].pixelformat;
+	strlcpy(f->description, s6dp_enum_fmt[i].description,
+		sizeof(f->description));
+	f->flags = 0;
+	return 0;
+}
+
+static int s6v4l_enum_fmt_out(struct file *file, void *priv,
+			struct v4l2_fmtdesc *f)
+{
+	u32 i = f->index;
+	if (i > 0)
+		return -EINVAL;
+	/* Only 422 for now */
+	f->pixelformat = s6dp_enum_fmt[1].pixelformat;
+	strlcpy(f->description, s6dp_enum_fmt[1].description,
+		sizeof(f->description));
+	f->flags = 0;
+	return 0;
+}
+
+static int s6v4l_cropcap(struct file *file, void *priv, struct v4l2_cropcap *c)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (c->type != CURRENT_BUF_TYPE(pd))
+		return -EINVAL;
+
+	if (!pd->link || !pd->link->cropcap)
+		return -EINVAL;
+
+	return pd->link->cropcap(pd->link->context, c);
+}
+
+static int s6v4l_s_crop(struct file *file, void *priv, struct v4l2_crop *c)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	int ret;
+
+	if (c->type != CURRENT_BUF_TYPE(pd))
+		return -EINVAL;
+
+	if (!pd->link || !pd->link->s_crop)
+		return -EINVAL;
+
+	ret = pd->link->s_crop(pd->link->context, c,
+			       pd->cur.state >= DP_STATE_READY);
+
+	return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_g_crop(struct file *file, void *priv, struct v4l2_crop *c)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (c->type != CURRENT_BUF_TYPE(pd))
+		return -EINVAL;
+
+	if (!pd->link || !pd->link->g_crop)
+		return -EINVAL;
+
+	return pd->link->g_crop(pd->link->context, c);
+}
+
+static int s6v4l_try_fmt(struct file *file, void *priv,
+			struct v4l2_format *f)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	int cwidth, cheight, cbytesperline, aligned = 1;
+	if (!pd->link || !pd->link->s_fmt || !pd->link->g_mode)
+		return 0;
+
+	pd->link->s_fmt(pd->link->context, 1, &f->fmt.pix, 1);
+
+	switch (f->fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P;
+		f->fmt.pix.width &= ~1;
+		break;
+	default:
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV444P;
+	}
+	if (f->fmt.pix.field == V4L2_FIELD_ALTERNATE)
+		f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+	cheight = f->fmt.pix.height;
+	switch (f->fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_YUV444P:
+		cwidth = f->fmt.pix.width;
+		break;
+	case V4L2_PIX_FMT_YUV420:
+		cheight = f->fmt.pix.height / 2;
+	case V4L2_PIX_FMT_YUV422P:
+		cwidth = f->fmt.pix.width / 2;
+		break;
+	default:
+		cwidth = 0;
+	}
+	if (aligned) {
+		f->fmt.pix.bytesperline = s6dp_bytealigned(f->fmt.pix.width);
+		cbytesperline = s6dp_bytealigned(cwidth);
+	} else {
+		f->fmt.pix.bytesperline = f->fmt.pix.width;
+		cbytesperline = cwidth;
+	}
+	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height
+			       + cbytesperline * cheight * 2;
+	return 0;
+}
+
+static int s6v4l_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	unsigned i;
+
+	memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format));
+	if (pd->link && pd->link->g_fmt) {
+		i = pd->link->g_fmt(pd->link->context, &f->fmt.pix);
+		if (i < 0)
+			return i;
+	} else {
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+	}
+	f->fmt.pix.field = pd->cur.vfield;
+	if (f->fmt.pix.field == V4L2_FIELD_ALTERNATE)
+		f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+	f->fmt.pix.width = pd->cur.width;
+	f->fmt.pix.height = pd->cur.height;
+	f->fmt.pix.pixelformat = pd->cur.fourcc;
+	f->fmt.pix.bytesperline = s6dp_bytealigned(f->fmt.pix.width);
+	f->fmt.pix.priv = pd->cur.aligned;
+	f->fmt.pix.sizeimage = pd->cur.bufsize;
+	return 0;
+}
+
+static int s6v4l_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct video_device *dev = file->private_data;
+	struct s6dp *pd = video_get_drvdata(dev);
+	struct v4l2_pix_format pfmt;
+	int r, align;
+	if (pd->cur.state != DP_STATE_IDLE)
+		return -EBUSY;
+	r = s6v4l_try_fmt(file, dev, f);
+	if (r < 0)
+		return r;
+	if (pd->link && pd->link->s_fmt) {
+		pfmt = f->fmt.pix;
+		r = pd->link->s_fmt(pd->link->context, 0, &pfmt, 0);
+	}
+	r = s6v4l_update(pd, r);
+	if (r < 0)
+		return r;
+
+	align = f->fmt.pix.priv & 1;
+	pd->cur.vfield = f->fmt.pix.field;
+	pd->cur.colorspace = f->fmt.pix.colorspace;
+	r = s6dp_set_current(dev, f->fmt.pix.pixelformat, align);
+	return r;
+}
+
+static int s6v4l_enum_input(struct file *file, void *fh, struct v4l2_input *inp)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (!pd->link || !pd->link->dir.ingress.e_inp)
+		return -EINVAL;
+
+	return pd->link->dir.ingress.e_inp(pd->link->context, inp);
+}
+
+static int s6v4l_enum_output(struct file *file, void *fh,
+			     struct v4l2_output *outp)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (!pd->link || !pd->link->dir.egress.e_outp)
+		return -EINVAL;
+
+	return pd->link->dir.egress.e_outp(pd->link->context, outp);
+}
+
+static int s6v4l_g_input(struct file *file, void *fh,  unsigned int *i)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (!pd->link || !pd->link->dir.ingress.s_inp)
+		return -EINVAL;
+
+	*i = pd->num_io;
+	return 0;
+}
+
+static int s6v4l_g_output(struct file *file, void *fh,  unsigned int *i)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	if (!pd->link || !pd->link->dir.egress.s_outp)
+		return -EINVAL;
+
+	*i = pd->num_io;
+	return 0;
+}
+
+static int s6v4l_s_input(struct file *file, void *fh,  unsigned int i)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+	int ret = -EINVAL;
+
+	if (pd->link && pd->link->dir.ingress.s_inp) {
+		ret = pd->link->dir.ingress.s_inp(pd->link->context, i,
+						  pd->cur.state
+							>= DP_STATE_READY);
+		if (ret >= 0)
+			pd->num_io = i;
+	}
+
+	return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_s_output(struct file *file, void *fh,  unsigned int i)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+	int ret = -EINVAL;
+
+	if (pd->link && pd->link->dir.egress.s_outp) {
+		ret = pd->link->dir.egress.s_outp(pd->link->context, i,
+					       pd->cur.state >= DP_STATE_READY);
+		if (ret >= 0)
+			pd->num_io = i;
+	}
+
+	return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_querycap(struct file *file, void *fh,
+			  struct v4l2_capability *cap)
+{
+	struct video_device *dev = video_devdata(file);
+	struct s6dp *pd = video_get_drvdata(dev);
+
+	strcpy(cap->driver, "s6dp");
+	strcpy(cap->card, "Stretch data port");
+	sprintf(cap->bus_info, "Data port %i", pd->port);
+	cap->version = DRIVER_VERSION_NUM;
+	if (pd->ext.egress)
+		cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT;
+	else
+		cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+	return 0;
+}
+
+static const struct v4l2_file_operations s6v4l_video_fops = {
+	.owner			= THIS_MODULE,
+	.open			= s6dp_video_open,
+	.release		= s6dp_video_close,
+	.get_unmapped_area	= s6dp_video_get_unmapped_area,
+	.mmap			= s6dp_video_mmap,
+	.poll			= s6dp_video_poll,
+	.ioctl			= s6dp_video_ioctl,
+};
+
+static const struct v4l2_ioctl_ops capture_v4l_ioctl_ops = {
+	.vidioc_querycap = s6v4l_querycap,
+	.vidioc_enum_fmt_vid_cap = s6v4l_enum_fmt_cap,
+	.vidioc_g_fmt_vid_cap = s6v4l_g_fmt,
+	.vidioc_s_fmt_vid_cap = s6v4l_s_fmt,
+	.vidioc_try_fmt_vid_cap = s6v4l_try_fmt,
+	.vidioc_reqbufs = s6v4l_reqbufs,
+	.vidioc_querybuf = s6v4l_querybuf,
+	.vidioc_qbuf = s6v4l_qbuf,
+	.vidioc_dqbuf = s6v4l_dqbuf,
+	.vidioc_streamon = s6v4l_streamon,
+	.vidioc_streamoff = s6v4l_streamoff,
+	.vidioc_s_std = s6v4l_s_std,
+	.vidioc_enum_input = s6v4l_enum_input,
+	.vidioc_g_input = s6v4l_g_input,
+	.vidioc_s_input = s6v4l_s_input,
+	.vidioc_cropcap = s6v4l_cropcap,
+	.vidioc_g_crop = s6v4l_g_crop,
+	.vidioc_s_crop = s6v4l_s_crop,
+};
+
+static const struct v4l2_ioctl_ops output_v4l_ioctl_ops = {
+	.vidioc_querycap = s6v4l_querycap,
+	.vidioc_enum_fmt_vid_out = s6v4l_enum_fmt_out,
+	.vidioc_g_fmt_vid_out = s6v4l_g_fmt,
+	.vidioc_s_fmt_vid_out = s6v4l_s_fmt,
+	.vidioc_try_fmt_vid_out = s6v4l_try_fmt,
+	.vidioc_reqbufs = s6v4l_reqbufs,
+	.vidioc_querybuf = s6v4l_querybuf,
+	.vidioc_qbuf = s6v4l_qbuf,
+	.vidioc_dqbuf = s6v4l_dqbuf,
+	.vidioc_streamon = s6v4l_streamon,
+	.vidioc_streamoff = s6v4l_streamoff,
+	.vidioc_s_std = s6v4l_s_std,
+	.vidioc_enum_output = s6v4l_enum_output,
+	.vidioc_g_output = s6v4l_g_output,
+	.vidioc_s_output = s6v4l_s_output,
+	.vidioc_cropcap = s6v4l_cropcap,
+	.vidioc_g_crop = s6v4l_g_crop,
+	.vidioc_s_crop = s6v4l_s_crop,
+};
+
+
+static int probe_one(struct platform_device *pdev, int irq,
+		     struct video_device **devs, struct s6dp_link *link,
+		     void __iomem *dpbase, void __iomem *dmac, u32 physbase)
+{
+	struct video_device *dev;
+	struct s6dp *pd;
+	int index, res = -ENOMEM;
+
+	dev = video_device_alloc();
+	if (!dev) {
+		printk(DRV_ERR "video device alloc failed.\n");
+		goto err_allocd;
+	}
+	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+	if (!pd) {
+		printk(DRV_ERR "video device alloc failed.\n");
+		goto err_allocp;
+	}
+	pd->ext.egress = link->is_egress;
+	strlcpy(dev->name, pdev->name, sizeof(dev->name));
+	dev->fops = &s6v4l_video_fops;
+	dev->release = video_device_release;
+	dev->tvnorms = 0;
+	dev->parent = &pdev->dev;
+	if (pd->ext.egress)
+		dev->ioctl_ops = &output_v4l_ioctl_ops;
+	else
+		dev->ioctl_ops = &capture_v4l_ioctl_ops;
+	video_set_drvdata(dev, pd);
+	pd->irq = irq;
+	pd->dp = dpbase;
+	pd->dmac = (u32)dmac;
+	for (index = 0; !(link->port_mask & (1 << index)); index++)
+		;
+	if (link->port_mask != (1 << index)) {
+		printk(DRV_ERR "multi port mode not implemented\n");
+		goto err_videor;
+	}
+	pd->port = index;
+	pd->dataram = physbase + S6_DP_DATARAM(index);
+	pd->cur.state = DP_STATE_UNUSED;
+	pd->frames = NULL;
+	pd->nrframes = 0;
+	pd->link = link;
+	INIT_LIST_HEAD(&pd->idleq);
+	INIT_LIST_HEAD(&pd->busyq);
+	INIT_LIST_HEAD(&pd->fullq);
+	init_waitqueue_head(&pd->wait);
+	spin_lock_init(&pd->lock);
+	if (video_register_device_index(dev, VFL_TYPE_GRABBER, link->minor,
+					index)) {
+		printk(DRV_ERR "video_register_device failed!\n");
+		res = -ENODEV;
+		goto err_videor;
+	}
+	s6dp_reset_port(dev);
+	*devs = dev;
+	return 0;
+
+err_videor:
+	kfree(pd);
+err_allocp:
+	video_device_release(dev);
+err_allocd:
+	return res;
+}
+
+static int __devinit s6dp_probe(struct platform_device *pdev)
+{
+	int i, ret, irq;
+	unsigned in_use;
+	struct video_device **devs;
+	struct s6dp_link *links;
+	void __iomem *dpbase, *dmacbase;
+	struct resource *res, *regs, *dmac;
+	if (!pdev->dev.platform_data) {
+		printk(DRV_ERR "no platform data given\n");
+		return -EINVAL;
+	}
+	devs = kzalloc(DP_NB_PORTS * sizeof(*devs), GFP_KERNEL);
+	if (!devs) {
+		printk(DRV_ERR "video device alloc failed.\n");
+		return -ENOMEM;
+	}
+	irq = platform_get_irq(pdev, 0);
+	ret = request_irq(irq, &s6dp_interrupt, 0, DRV_NAME, devs);
+	if (ret) {
+		printk(DRV_ERR "irq request failed: %d\n", irq);
+		goto err_free_mem;
+	}
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -EINVAL;
+		goto err_free_irq;
+	}
+	regs = request_mem_region(res->start, res->end - res->start + 1,
+				  pdev->name);
+	if (!res) {
+		ret = -EBUSY;
+		goto err_free_irq;
+	}
+	dpbase = ioremap_nocache(regs->start, regs->end - regs->start + 1);
+	if (!dpbase) {
+		ret = -ENOMEM;
+		goto err_free_regs;
+	}
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		ret = -EINVAL;
+		goto err_unmap_regs;
+	}
+	dmac = request_mem_region(res->start, res->end - res->start + 1,
+				  pdev->name);
+	if (!dmac) {
+		ret = -EBUSY;
+		goto err_unmap_regs;
+	}
+	dmacbase = ioremap_nocache(dmac->start, dmac->end - dmac->start + 1);
+	if (!dmacbase) {
+		ret = -ENOMEM;
+		goto err_free_dmac;
+	}
+	i = 0;
+	in_use = 0;
+	for (links = pdev->dev.platform_data; links->port_mask; links++) {
+		if (in_use & links->port_mask) {
+			printk(DRV_ERR "port already in use - skipping\n");
+			continue;
+		}
+		ret = probe_one(pdev, irq, &devs[i], links, dpbase, dmacbase,
+				regs->start);
+		if (ret)
+			goto err_free_devs;
+		in_use |= links->port_mask;
+		i++;
+	}
+	platform_set_drvdata(pdev, devs);
+	return 0;
+
+err_free_devs:
+	while (i--) {
+		if (devs[i]) {
+			struct s6dp *pd = video_get_drvdata(devs[i]);
+			video_unregister_device(devs[i]);
+			kfree(pd);
+			video_device_release(devs[i]);
+		}
+	}
+	iounmap(dmacbase);
+err_free_dmac:
+	release_mem_region(dmac->start, dmac->end - dmac->start + 1);
+err_unmap_regs:
+	iounmap(dpbase);
+err_free_regs:
+	release_mem_region(regs->start, regs->end - regs->start + 1);
+err_free_irq:
+	free_irq(irq, devs);
+err_free_mem:
+	kfree(devs);
+	return ret;
+}
+
+static int __devexit s6dp_remove(struct platform_device *pdev)
+{
+	struct video_device **devs = platform_get_drvdata(pdev);
+	int i;
+	platform_set_drvdata(pdev, NULL);
+	for (i = 0; i < DP_NB_PORTS; i++) {
+		struct video_device *dev = devs[i];
+		if (dev) {
+			struct s6dp *pd = video_get_drvdata(dev);
+			video_unregister_device(dev);
+			kfree(pd);
+			video_device_release(dev);
+		}
+	}
+	i = platform_get_irq(pdev, 0);
+	free_irq(i, devs);
+	kfree(devs);
+	return 0;
+}
+
+static struct platform_driver s6dp_driver = {
+	.probe = s6dp_probe,
+	.remove = __devexit_p(s6dp_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s6dp_init(void)
+{
+	printk(DRV_INFO "S6 video driver <info@emlix.com>\n");
+	return platform_driver_register(&s6dp_driver);
+}
+
+static void __exit s6dp_exit(void)
+{
+	platform_driver_unregister(&s6dp_driver);
+}
+
+module_init(s6dp_init);
+module_exit(s6dp_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("S6105 on chip video driver");
+MODULE_AUTHOR("Fabian Godehardt, Hannes Weiner, "
+	"Oskar Schirmer, Daniel Gloeckner");
diff --git a/drivers/media/video/s6dp/s6dp.h b/drivers/media/video/s6dp/s6dp.h
new file mode 100644
index 0000000..4f299b7
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp.h
@@ -0,0 +1,121 @@
+/*
+ * drivers/media/video/s6dp/s6dp.h
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2008 emlix GmbH <info@emlix.com>
+ * Authors:	Fabian Godehardt <fg@emlix.com>
+ *		Oskar Schirmer <os@emlix.com>
+ */
+
+#ifndef __ASM_XTENSA_S6105_DP_H
+#define __ASM_XTENSA_S6105_DP_H
+
+#define S6_DP_CHAN_PER_PORT	4
+
+/* global data port setup */
+#define S6_DP_INT_STATUS		0x00
+#define S6_DP_INT_LOWWMARK(p)			(p)
+#define S6_DP_INT_PENDGCNT(p)			((p) + 4)
+#define S6_DP_INT_TERMCNT(p)			((p) + 8)
+#define S6_DP_INT_ERR_INT			12
+#define S6_DP_INT_ENABLE		0x04
+#define S6_DP_INT_DMAERR			16
+#define S6_DP_INT_UNDEROVERRUN(p)		((p) + 20)
+#define S6_DP_INT_WRONGPIXEL(p)			((p) * 2 + 24)
+#define S6_DP_INT_WRONGLINES(p)			((p) * 2 + 25)
+#define S6_DP_INT_RAW			0x08
+#define S6_DP_INT_CLEAR			0x0c
+#define S6_DP_INT_SET			0x10
+#define S6_DP_INT_UNMAP_RAW0		0x14
+#define S6_DP_INT_UNMAP_RAW1		0x18
+#define S6_DP_INT_UNMAP_RAW1_DP2_BT1120ERR	18
+#define S6_DP_INT_UNMAP_RAW1_DP0_BT1120ERR	19
+#define S6_DP_DP_CLK_SETTING		0x40
+#define S6_DP_DP_CLK_SETTING_CLK_MUX(p)		((p) * 4)
+#define S6_DP_DP_CLK_SETTING_CLK_MUX_MASK		3
+#define S6_DP_VIDEO_OUT_DLL_SEL		0x50
+#define S6_DP_VIDEO_REF_DLL_SEL		0x54
+#define S6_DP_VIDEO_FBK_DLL_SEL		0x58
+#define S6_DP_VIDEO_ENABLE		0x80
+#define S6_DP_VIDEO_ENABLE_ENABLE(p)		((p) * 8)
+#define S6_DP_VIDEO_DMA_CFG		0x84
+#define S6_DP_VIDEO_DMA_CFG_BURST_BITS(p)	((p) * 8)
+#define S6_DP_VIDEO_CFG(p)		((p) * 0x4 + 0x90)
+#define S6_DP_VIDEO_CFG_8_OR_10			0
+#define S6_DP_VIDEO_CFG_IN_OR_OUT		1
+#define S6_DP_VIDEO_CFG_FRAMING			2
+#define S6_DP_VIDEO_CFG_MODE			3
+#define S6_DP_VIDEO_CFG_MODE_422_SERIAL			0
+#define S6_DP_VIDEO_CFG_MODE_444_SERIAL			1
+#define S6_DP_VIDEO_CFG_MODE_422_PARALLEL		2
+#define S6_DP_VIDEO_CFG_MODE_444_PARALLEL		3
+#define S6_DP_VIDEO_CFG_MODE_422_SERIAL_CASCADE		4
+#define S6_DP_VIDEO_CFG_MODE_444_SERIAL_CASCADE		5
+#define S6_DP_VIDEO_CFG_MODE_422_PARALLEL_CASCADE	6
+#define S6_DP_VIDEO_CFG_MODE_RAW			7
+#define S6_DP_VIDEO_CFG_MODE_FIFO8			8
+#define S6_DP_VIDEO_CFG_MODE_FIFO16			9
+#define S6_DP_VIDEO_CFG_MODE_FIFO32			10
+#define S6_DP_VIDEO_CFG_MODE_STREAM8			11
+#define S6_DP_VIDEO_CFG_MODE_STREAM16			12
+#define S6_DP_VIDEO_CFG_MODE_STREAM32			13
+#define S6_DP_VIDEO_CFG_MODE_STREAM8_CASCADE		14
+#define S6_DP_VIDEO_CFG_MODE_STREAM16_CASCADE		15
+#define S6_DP_VIDEO_CFG_INTERL_OR_PROGR		8
+#define S6_DP_VIDEO_CFG_1120_VIDEO_MODE		9
+#define S6_DP_VIDEO_CFG_ANCILLARY_DATA		10
+#define S6_DP_VIDEO_CFG_VSYNC_POL		12
+#define S6_DP_VIDEO_CFG_HSYNC_POL		13
+#define S6_DP_VIDEO_CFG_BLANK_POL		14
+#define S6_DP_VIDEO_CFG_FIELD_CTRL		15
+#define S6_DP_VIDEO_CFG_BLANK_CTRL		16
+#define S6_DP_VIDEO_CFG_RELAX_MODE		21
+#define S6_DP_VIDEO_CFG_MICRON_MODE		22
+#define S6_DP_VIDEO_BLANK(p)		((p) * 0x4 + 0xa0)
+#define S6_DP_VIDEO_BAD_FRAME_NUM(p)	((p) * 0x4 + 0xc0)
+#define S6_DP_VIDEO_BAD_PIXEL_CNT(p)	((p) * 0x8 + 0xd0)
+#define S6_DP_VIDEO_BAD_LINE_CNT(p)	((p) * 0x8 + 0xd4)
+
+/* per port configuration registers */
+#define S6_DP_PIXEL_TOTAL		0x00
+#define S6_DP_PIXEL_ACTIVE		0x04
+#define S6_DP_PIXEL_OFFSET		0x08
+#define S6_DP_PIXEL_PADDING		0x0c
+#define S6_DP_ANC_PIXEL_ACTIVE		0x10
+#define S6_DP_ANC_PIXEL_OFFSET		0x14
+#define S6_DP_LINE_TOTAL		0x18
+#define S6_DP_LINE_ODD_TOTAL		0x1c
+#define S6_DP_LINE_ODD_ACTIVE		0x20
+#define S6_DP_LINE_ODD_OFFSET		0x24
+#define S6_DP_LINE_EVEN_ACTIVE		0x28
+#define S6_DP_LINE_EVEN_OFFSET		0x2c
+#define S6_DP_LINE_ODD_ANC_ACTIVE	0x30
+#define S6_DP_LINE_ODD_ANC_OFFSET	0x34
+#define S6_DP_LINE_EVEN_ANC_ACTIVE	0x38
+#define S6_DP_LINE_EVEN_ANC_OFFSET	0x3c
+#define S6_DP_ODD_VSYNC_LENGTH		0x40
+#define S6_DP_ODD_VSYNC_OFFSET		0x44
+#define S6_DP_EVEN_VSYNC_LENGTH		0x48
+#define S6_DP_EVEN_VSYNC_OFFSET		0x4c
+#define S6_DP_ODD_HSYNC_LENGTH		0x50
+#define S6_DP_ODD_HSYNC_OFFSET		0x54
+#define S6_DP_EVEN_HSYNC_LENGTH		0x58
+#define S6_DP_EVEN_HSYNC_OFFSET		0x5c
+
+#define S6_DP_FRAME_COUNT		0x60
+#define S6_DP_TSI_TIMESTAMP_UPDATE	0x64
+#define S6_DP_TSI_TIMESTAMP_HI		0x68
+#define S6_DP_TSI_TIMESTAMP_LO		0x6c
+#define S6_DP_CBCR_DMA_CONVERT		0x70
+#define S6_DP_Y_DMA_CONVERT		0x74
+#define S6_DP_ANC_DMA_CONVERT		0x78
+
+#define S6_DP_CFG_BASE(n)		((n) * 0x80 + 0x100)
+#define S6_DP_CHAN_OFFSET(n)		((n) * 0x100)
+#define S6_DP_DATARAM(port)		((port) * S6_DP_CHAN_PER_PORT * 0x100 \
+						+ 0x1000)
+
+#endif /* __ASM_XTENSA_S6105_DP_H */
diff --git a/include/media/s6dp-link.h b/include/media/s6dp-link.h
new file mode 100644
index 0000000..d1197da
--- /dev/null
+++ b/include/media/s6dp-link.h
@@ -0,0 +1,63 @@
+#ifndef __S6DP_LINK_H__
+#define __S6DP_LINK_H__
+
+#include <linux/videodev2.h>
+
+struct s6dp_mode {
+	unsigned int type:4;
+	unsigned int progressive:1;
+	unsigned int embedded_sync:1;
+	unsigned int micron_mode:1;
+	unsigned int vsync_pol:1;
+	unsigned int hsync_pol:1;
+	unsigned int blank_pol:1;
+	unsigned int field_ctrl:1;
+	unsigned int blank_ctrl:1;
+	unsigned int relaxed_framing:1;
+	unsigned int ten_bit:1;
+	unsigned int line_and_crc:1;
+	u16 pixel_total;
+	u16 pixel_offset;
+	u16 pixel_active;
+	u16 pixel_padding;
+	u16 hsync_offset;
+	u16 hsync_len;
+	u16 framelines;
+	u16 odd_vsync_offset;
+	u16 odd_vsync_len;
+	u16 odd_first;
+	u16 odd_active;
+	u16 odd_total;
+	u16 even_vsync_offset;
+	u16 even_vsync_len;
+	u16 even_first;
+	u16 even_active;
+};
+
+struct s6dp_link {
+	void *context;
+	unsigned port_mask:4;
+	unsigned is_egress:1;
+	int minor;
+	void (*g_mode)(void *ctx, struct s6dp_mode *mode);
+	int (*cropcap)(void *ctx, struct v4l2_cropcap *cap);
+	int (*s_crop)(void *ctx, struct v4l2_crop *crop, int busy);
+	int (*g_crop)(void *ctx, struct v4l2_crop *crop);
+	int (*e_std)(void *ctx, struct v4l2_standard *std);
+	int (*s_std)(void *ctx, v4l2_std_id *mask, int busy);
+	int (*s_fmt)(void *ctx, int try_fmt, struct v4l2_pix_format *fmt,
+		     int busy);
+	int (*g_fmt)(void *ctx, struct v4l2_pix_format *fmt);
+	union {
+		struct {
+			int (*e_inp)(void *ctx, struct v4l2_input *inp);
+			int (*s_inp)(void *ctx, unsigned int nr, int busy);
+		} ingress;
+		struct {
+			int (*e_outp)(void *ctx, struct v4l2_output *outp);
+			int (*s_outp)(void *ctx, unsigned int nr, int busy);
+		} egress;
+	} dir;
+};
+
+#endif
-- 
1.6.2.107.ge47ee


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

* [patch 2/5] s6000 data port: custom video mode support
  2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
@ 2009-03-26 14:36 ` Daniel Glöckner
  2009-03-26 14:36 ` [patch 3/5] s6000 data port: canonical modes Daniel Glöckner
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Chris Zankel, linux-media, Daniel Glöckner, Oskar Schirmer

Extend s6dp driver to support direct MPEG2 and data streaming modes.
Provide ioctl access to select specific modes.

Signed-off-by: Oskar Schirmer <os@emlix.com>
---
 .../platforms/s6105/include/platform/ioctl.h       |   59 +++
 drivers/media/video/s6dp/s6dp.c                    |  393 +++++++++++++++++--
 2 files changed, 410 insertions(+), 42 deletions(-)

diff --git a/arch/xtensa/platforms/s6105/include/platform/ioctl.h b/arch/xtensa/platforms/s6105/include/platform/ioctl.h
index 0fc6e2c..da88b4c 100644
--- a/arch/xtensa/platforms/s6105/include/platform/ioctl.h
+++ b/arch/xtensa/platforms/s6105/include/platform/ioctl.h
@@ -14,4 +14,63 @@
 #define S6IOCTL_ISEF_PRELOAD		_IO(56, 1)
 #define S6IOCTL_ISEF_INVALIDATE		_IO(56, 2)
 
+#define MODE_CUSTOM_VIDEO	28
+#define ITU_H222_TRANS		29	/* MPEG2 TS, 188 bytes */
+#define ITU_H222_TRANS_RS	30	/* MPEG2 TS, valid RS, 204 bytes */
+#define ITU_H222_TRANS_RS_DUMMY	31	/* MPEG2 TS, invalid RS, 204 bytes */
+#define STREAM8			32
+#define STREAM16		33
+#define STREAM32		34
+
+#define NUM_MODES		35
+#define MODE_LAST_VIDEO		MODE_CUSTOM_VIDEO
+
+struct s6dp_ioctl_config {
+	unsigned char mode;
+	unsigned char lane;
+	unsigned char is_10bit;
+	unsigned char micron_mode;
+	unsigned char use_1120_line_and_crc;
+	unsigned char ext_framing;
+	unsigned char vsync_pol;
+	unsigned char hsync_pol;
+	unsigned char blank_pol;
+	unsigned char field_ctrl;
+	unsigned char blank_ctrl;
+	unsigned char relaxed_framing_mode;
+	unsigned int desc_size;
+	struct {
+		unsigned int width;
+		unsigned int height;
+		unsigned char portsperstream;
+		unsigned char greyperchroma;
+		unsigned char progressive;
+		struct {
+			unsigned int pixel_total;
+			unsigned int pixel_offset;
+			unsigned int pixel_padding;
+			unsigned int line_total;
+			unsigned int line_odd_total;
+			unsigned int line_odd_offset;
+			unsigned int line_even_offset;
+			unsigned int odd_vsync_len;
+			unsigned int odd_vsync_offset;
+			unsigned int even_vsync_len;
+			unsigned int even_vsync_offset;
+			unsigned int odd_hsync_len;
+			unsigned int odd_hsync_offset;
+			unsigned int even_hsync_len;
+			unsigned int even_hsync_offset;
+		} reg;
+	} custom;
+	struct {
+		int pix_start;
+		unsigned int line_start;
+		unsigned int line_width;
+		unsigned int num_lines;
+	} anc_placement[2];
+};
+
+#define S6IOCTL_DP_CONFIG	_IOW(56, 9, struct s6dp_ioctl_config)
+
 #endif /* __XTENSA_S6105_IOCTL_H */
diff --git a/drivers/media/video/s6dp/s6dp.c b/drivers/media/video/s6dp/s6dp.c
index 434cec5..9f349be 100644
--- a/drivers/media/video/s6dp/s6dp.c
+++ b/drivers/media/video/s6dp/s6dp.c
@@ -31,6 +31,7 @@
 #include <linux/io.h>
 #include <variant/dmac.h>
 #include <variant/hardware.h>
+#include <platform/ioctl.h>
 #include "s6dp.h"
 
 #define DRV_NAME "s6dp"
@@ -40,6 +41,8 @@
 
 #define DP_NB_PORTS	(S6_DPDMA_NB / S6_DP_CHAN_PER_PORT)
 
+#define REPEAT_IN_STREAM_MODE	1
+
 /* device not opened */
 #define DP_STATE_UNUSED	0
 /* after open */
@@ -80,8 +83,10 @@ struct s6dp {
 	wait_queue_head_t wait;
 	u32 outstanding;
 	struct {
+		u8 modenr; /* -- FIXME */
 		u8 state;
 		u8 aligned:1;
+		u8 custom:1;
 		u8 framerepeat:1;
 		u8 progressive:1;
 
@@ -121,6 +126,7 @@ struct s6dp {
 		u8 is_10bit:1;
 		u8 micron:1;
 		u8 egress:1;
+		u8 cascade:1;
 		u8 use_1120_line_and_crc:1;
 		u8 ext_framing:1;
 		u8 vsync_pol:1;
@@ -130,6 +136,14 @@ struct s6dp {
 		u8 blank_ctrl:1;
 		u8 relaxed_framing_mode:1;
 		u32 desc_size;
+		struct anc_placement {
+			struct anc_placement_bbox {
+				s32 pix_start;
+				u32 line_start;
+				u32 line_width;
+				u32 num_lines;
+			} field[2];
+		} anc_placement;
 	} ext;
 	unsigned int num_io;
 };
@@ -303,6 +317,34 @@ static irqreturn_t s6dp_interrupt(int irq, void *dev_id)
 	return ret;
 }
 
+static inline int s6dp_is_anc_data_on(struct s6dp *pd)
+{
+	/*
+	 * if any of the fields in the bounding box are zero, ancillary data
+	 * is not turned on
+	 * the lane specifier can also affect if ancillary data is on
+	 * lane==0 is always off.
+	 * In SD modes, lane==2 or 3 also turns off ancillary data
+	 * In HD 4:2:2, lane==3 turns off ancillary data
+	 */
+	return pd->port != 0 &&
+		pd->ext.anc_placement.field[0].pix_start  != 0 &&
+		pd->ext.anc_placement.field[0].line_start != 0 &&
+		pd->ext.anc_placement.field[0].line_width != 0 &&
+		pd->ext.anc_placement.field[0].num_lines  != 0 &&
+		pd->ext.anc_placement.field[1].pix_start  != 0 &&
+		pd->ext.anc_placement.field[1].line_start != 0 &&
+		pd->ext.anc_placement.field[1].line_width != 0 &&
+		pd->ext.anc_placement.field[1].num_lines  != 0;
+}
+
+static unsigned s6dp_get_k_size(struct s6dp *pd)
+{
+	if (!s6dp_is_anc_data_on(pd))
+		return 0;
+	BUG(); /* FIXME */
+}
+
 static int s6dp_dma_init(struct video_device *dev)
 {
 	struct s6dp *pd = video_get_drvdata(dev);
@@ -315,7 +357,7 @@ static int s6dp_dma_init(struct video_device *dev)
 	for (i = (1 << (burstsize - 4)) - 1; n & i; i >>= 1)
 		burstsize--;
 
-	n = 3;
+	n = s6dp_is_anc_data_on(pd) ? 4 : 3;
 	i = 0;
 	do {
 		int ret;
@@ -341,8 +383,13 @@ static int s6dp_dma_init(struct video_device *dev)
 		}
 	} while (++i < n);
 
-	pd->cur.framerepeat = 1;
-	s6dmac_dp_setup_group(pd->dmac, pd->port, n, 1);
+	pd->cur.framerepeat = 0;
+	if (pd->cur.modenr <= MODE_LAST_VIDEO || REPEAT_IN_STREAM_MODE) {
+		pd->cur.framerepeat = 1;
+		if (pd->cur.modenr > MODE_LAST_VIDEO)
+			n = 1;
+		s6dmac_dp_setup_group(pd->dmac, pd->port, n, 1);
+	}
 
 	DP_REG_W(pd, S6_DP_VIDEO_DMA_CFG, (DP_REG_R(pd, S6_DP_VIDEO_DMA_CFG)
 		& ~(7 << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)))
@@ -366,7 +413,7 @@ static void s6dp_dma_free(struct video_device *dev)
 	if (pd->cur.state < DP_STATE_ACTIVE)
 		return;
 
-	n = 3;
+	n = s6dp_is_anc_data_on(pd) ? 4 : 3;
 	i = 0;
 	do {
 		s6dmac_release_chan(pd->dmac,
@@ -377,16 +424,46 @@ static void s6dp_dma_free(struct video_device *dev)
 static int s6dp_setup_stream(struct video_device *dev)
 {
 	struct s6dp *pd = video_get_drvdata(dev);
-	unsigned i, n, y;
+	unsigned i, m, k_div;
 	unsigned long flags;
 
-	i = pd->cur.portsperstream;
+	i = 0;
+	m = pd->cur.modenr;
+	if (m < NUM_MODES) {
+		if (m <= MODE_LAST_VIDEO)
+			i = pd->cur.portsperstream;
+		else if (m == STREAM8)
+			i = 1;
+		else if (m == STREAM16)
+			i = 2;
+		else if (m == STREAM32)
+			i = 4;
+	}
 	if (i != 1) {
 		printk(DRV_ERR "multi port mode not implemented\n");
 		/* needs cross device checking for free channels */
 		return -EINVAL;
 	}
+	if ((i == 0) || (i > 3) || (pd->port % i)) {
+		printk(DRV_ERR "invalid mode (%u, port %u, ports %u)\n",
+			m, pd->port, i);
+		return -EINVAL;
+	}
+	if ((i > 2 || (i == 2 && pd->cur.greyperchroma == 1))
+	 && pd->ext.cascade) {
+		printk(DRV_ERR "cascade mode not available (ports %u)\n", i);
+		return -EINVAL;
+	}
 	pd->cur.portsperstream = i; /* FIXME -> set_current */
+	if ((pd->ext.anc_placement.field[0].pix_start &&
+			(pd->ext.anc_placement.field[0].pix_start !=
+			pd->ext.anc_placement.field[1].pix_start))
+		|| (pd->ext.anc_placement.field[0].line_start &&
+			(pd->ext.anc_placement.field[0].line_start !=
+			pd->ext.anc_placement.field[1].line_start))) {
+		printk(DRV_ERR "error - check bounding boxes\n");
+		return -EINVAL;
+	}
 	/* write port configuration (24 regs. minus ANC stuff, see below) */
 	DP_PREG_W(pd, S6_DP_PIXEL_TOTAL, pd->cur.pixel_total);
 	DP_PREG_W(pd, S6_DP_PIXEL_ACTIVE,
@@ -409,12 +486,59 @@ static int s6dp_setup_stream(struct video_device *dev)
 	DP_PREG_W(pd, S6_DP_EVEN_HSYNC_LENGTH, pd->cur.even_hsync_len);
 	DP_PREG_W(pd, S6_DP_EVEN_HSYNC_OFFSET, pd->cur.even_hsync_offset);
 
-	DP_PREG_W(pd, S6_DP_ANC_PIXEL_ACTIVE, 0);
-	DP_PREG_W(pd, S6_DP_ANC_PIXEL_OFFSET, 0);
-	DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_ACTIVE, 0);
-	DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_OFFSET, 0);
-	DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_ACTIVE, 0);
-	DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_OFFSET, 0);
+	/* Program ancilliary data config, if required */
+	if (s6dp_is_anc_data_on(pd)) {
+		/*
+		 * k_div is:
+		 *	4 for SD 4:2:2,
+		 *	3 for SD 4:4:4,
+		 *	2 for HD 4:2:2,
+		 *	1 for HD 4:4:4
+		 */
+		if (m <= MODE_LAST_VIDEO) {
+			k_div = (pd->cur.portsperstream == 1) ? 4 : 2;
+			if (pd->cur.greyperchroma == 1)
+				k_div -= 1;
+		} else {
+			k_div = 1;
+		}
+		/* adjust for the fact that not all lanes are used in HD mode */
+		k_div *= pd->cur.portsperstream;
+		DP_PREG_W(pd, S6_DP_ANC_PIXEL_ACTIVE,
+			  pd->ext.anc_placement.field[0].line_width / k_div);
+		DP_PREG_W(pd, S6_DP_ANC_PIXEL_OFFSET,
+			  pd->ext.anc_placement.field[0].pix_start - 1);
+		DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_ACTIVE,
+			  pd->ext.anc_placement.field[0].num_lines);
+		DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_OFFSET,
+			  pd->ext.anc_placement.field[0].line_start - 1);
+		if ((m <= MODE_LAST_VIDEO) &&
+		    (pd->cur.progressive)) {
+			DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_ACTIVE, 0);
+			DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_OFFSET, 0);
+		} else {
+			/* NB:
+			 *  anc_placement.field[1].line_width ==
+			 *  anc_placement.field[0].line_width
+			 * and
+			 *  anc_placement.field[1].pix_start ==
+			 *  anc_placement.field[0].pix_start
+			 */
+			DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_ACTIVE,
+				  pd->ext.anc_placement.field[1].num_lines);
+			DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_OFFSET,
+				  pd->ext.anc_placement.field[1].line_start
+				   - 1);
+		}
+	} else {
+		k_div = 1;
+		DP_PREG_W(pd, S6_DP_ANC_PIXEL_ACTIVE, 0);
+		DP_PREG_W(pd, S6_DP_ANC_PIXEL_OFFSET, 0);
+		DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_ACTIVE, 0);
+		DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_OFFSET, 0);
+		DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_ACTIVE, 0);
+		DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_OFFSET, 0);
+	}
 
 	/*
 	 * Program the _dma_convert registers.  These values calculate for the
@@ -422,25 +546,80 @@ static int s6dp_setup_stream(struct video_device *dev)
 	 * In streaming mode, cbcr_dma_convert indicates the number of 16b
 	 * lines to do before issuing the last transfer.
 	 */
-	n = pd->cur.width / pd->cur.greyperchroma;
-	y = pd->cur.height;
-	i = pd->ext.is_10bit ? 12 : 16;
-	DP_PREG_W(pd, S6_DP_CBCR_DMA_CONVERT, ((n + i - 1) / i) * y);
-	i /= pd->cur.greyperchroma;
-	DP_PREG_W(pd, S6_DP_Y_DMA_CONVERT, ((n + i - 1) / i) * y);
-	DP_PREG_W(pd, S6_DP_ANC_DMA_CONVERT, 0);
+	if (m <= MODE_LAST_VIDEO) {
+		u32 n, y;
+		n = pd->cur.width / pd->cur.greyperchroma;
+		y = pd->cur.height;
+		i = pd->ext.is_10bit ? 12 : 16;
+		DP_PREG_W(pd, S6_DP_CBCR_DMA_CONVERT, ((n + i - 1) / i) * y);
+		i /= pd->cur.greyperchroma;
+		DP_PREG_W(pd, S6_DP_Y_DMA_CONVERT, ((n + i - 1) / i) * y);
+		n = s6dp_get_k_size(pd);
+		if (n) {
+			/* n /= num_anc_lines; */ /* FIXME */
+			n = (n + k_div - 1) / k_div;
+			/* n *= num_anc_lines; */ /* FIXME */
+		}
+		DP_PREG_W(pd, S6_DP_ANC_DMA_CONVERT, n);
+	} else {
+		/* Streaming modes */
+		/* TODO: Where did Kaiming come up with this stuff? */
+		if ((m == ITU_H222_TRANS) ||
+		    (m == ITU_H222_TRANS_RS_DUMMY))
+			i = 188;
+		else if (m == ITU_H222_TRANS_RS)
+			i = 204;
+		else
+			BUG();
+		DP_PREG_W(pd, S6_DP_CBCR_DMA_CONVERT, (i + 15) / 16);
+		DP_PREG_W(pd, S6_DP_ANC_DMA_CONVERT, 10); /* FIXME?!? */
+	}
 
 	/* Program dp_config. Function of mode and optional configs */
 	/* Video configuration */
-	i = (pd->cur.greyperchroma == 1 ? S6_DP_VIDEO_CFG_MODE_444_SERIAL
-					: S6_DP_VIDEO_CFG_MODE_422_SERIAL)
-						<< S6_DP_VIDEO_CFG_MODE;
-	i |= pd->ext.use_1120_line_and_crc << S6_DP_VIDEO_CFG_1120_VIDEO_MODE;
+	if (m <= MODE_LAST_VIDEO) {
+		const static u8 video_cfg_mode[2][2][2] = {
+			{ {	S6_DP_VIDEO_CFG_MODE_444_SERIAL,
+				S6_DP_VIDEO_CFG_MODE_444_SERIAL_CASCADE
+			  }, {	S6_DP_VIDEO_CFG_MODE_444_PARALLEL,
+				S6_DP_VIDEO_CFG_MODE_444_PARALLEL
+			  }
+			}, {
+			  {	S6_DP_VIDEO_CFG_MODE_422_SERIAL,
+				S6_DP_VIDEO_CFG_MODE_422_SERIAL_CASCADE
+			  }, {	S6_DP_VIDEO_CFG_MODE_422_PARALLEL,
+				S6_DP_VIDEO_CFG_MODE_422_PARALLEL_CASCADE
+			} }
+		};
+		i = video_cfg_mode[pd->cur.greyperchroma - 1]
+				[pd->cur.portsperstream > 1]
+				[pd->ext.cascade] << S6_DP_VIDEO_CFG_MODE;
+		i |= pd->ext.use_1120_line_and_crc
+				<< S6_DP_VIDEO_CFG_1120_VIDEO_MODE;
+	} else if (m >= STREAM8) {
+		const static u8 stream_cfg_mode[3][2] = {
+			{	S6_DP_VIDEO_CFG_MODE_STREAM8,
+				S6_DP_VIDEO_CFG_MODE_STREAM8_CASCADE
+			}, {	S6_DP_VIDEO_CFG_MODE_STREAM16,
+				S6_DP_VIDEO_CFG_MODE_STREAM16_CASCADE
+			}, {	S6_DP_VIDEO_CFG_MODE_STREAM32,
+				S6_DP_VIDEO_CFG_MODE_STREAM32
+			}
+		};
+		i = stream_cfg_mode[m - STREAM8]
+				[pd->ext.cascade] << S6_DP_VIDEO_CFG_MODE;
+	} else {
+		printk(DRV_ERR "unhandled mode: %d\n", m);
+		return -EINVAL;
+	}
 	/* Progressive / interlaced */
 	/* Micron mode: Must be progressive (regardless of what mode says) */
 	i |= pd->ext.micron << S6_DP_VIDEO_CFG_MICRON_MODE;
 	i |= (pd->ext.micron | pd->cur.progressive)
 			<< S6_DP_VIDEO_CFG_INTERL_OR_PROGR;
+	/* Ancillary data enabled  FIXME ?! */
+	if (s6dp_is_anc_data_on(pd))
+		i |= (pd->port & 0x3) << S6_DP_VIDEO_CFG_ANCILLARY_DATA;
 	/* External framing */
 	if (pd->ext.ext_framing) {
 		i |= 1 << S6_DP_VIDEO_CFG_FRAMING;
@@ -466,8 +645,52 @@ static int s6dp_setup_stream(struct video_device *dev)
 	i = DP_REG_R(pd, S6_DP_DP_CLK_SETTING)
 		& ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
 			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port));
-	i |= (pd->ext.egress ? 0 : 1) <<
-		S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+	if ((m <= MODE_LAST_VIDEO && pd->cur.portsperstream == 1)
+	    || m == STREAM8) {
+		/* SD and 8 bit streaming modes */
+		i |= (pd->ext.egress ? 0 : 1) <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+		if (pd->ext.cascade && !pd->ext.egress)
+			i |= 3 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 2);
+	} else if ((m <= MODE_LAST_VIDEO) &&
+			(pd->cur.portsperstream == 2)) {
+		/* HD 4:2:2 modes */
+		i &= ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 1));
+		i |= (pd->ext.egress ? 0 : 1) <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+		i |= 2 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 1);
+		if (pd->ext.cascade && !pd->ext.egress) {
+			i |= 3 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 2);
+			i |= 3 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 3);
+		}
+	} else if ((m <= MODE_LAST_VIDEO) &&
+			(pd->cur.portsperstream == 3)) {
+		/* HD 4:4:4 modes.  lane should be 0 */
+		i &= ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 1));
+		i &= ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 2));
+		i &= ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 3));
+		i |= (pd->ext.egress ? 0 : 1) <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+		i |= 2 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 1);
+		i |= 2 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 2);
+	} else if (m == STREAM16) {
+		i &= ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 1));
+		i |= (pd->ext.egress ? 0 : 1) <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+		i |= (pd->ext.egress ? 0 : 2) <<
+			S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 2);
+		if (pd->ext.cascade && !pd->ext.egress) {
+			i |= 3 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 1);
+			i |= 3 << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port + 2);
+		}
+	}
+	/* double check STREAM32 */
+	/*else if (m == DP_STREAM32) */
 	DP_REG_W(pd, S6_DP_DP_CLK_SETTING, i);
 
 	/* Initialize DP DMA registers for this stream */
@@ -491,7 +714,10 @@ static void _s6dp_reset_port(struct s6dp *pd)
 		  | (1 << S6_DP_INT_WRONGLINES(pd->port))));
 
 	/* Clear the enable bit for the entire DMA group */
-	s6dmac_dp_switch_group(pd->dmac, pd->port, 0);
+	if (pd->cur.modenr <= MODE_LAST_VIDEO || REPEAT_IN_STREAM_MODE)
+		s6dmac_dp_switch_group(pd->dmac, pd->port, 0);
+	else /* one channel streaming */
+		s6dmac_disable_chan(pd->dmac, pd->port * S6_DP_CHAN_PER_PORT);
 	pd->outstanding = 0;
 	spin_unlock_irqrestore(&pd->lock, flags);
 	/* wait for first channel's DMA to become disabled */
@@ -648,6 +874,7 @@ static int s6dp_video_close(struct file *file)
 	/* free buffer(s) */
 	s6dp_relbufs(dev);
 	pd->cur.state = DP_STATE_UNUSED;
+	pd->cur.custom = 0;
 	return 0;
 }
 
@@ -733,7 +960,71 @@ static long s6dp_video_ioctl(struct file *file, unsigned int cmd,
 {
 	struct video_device *dev = file->private_data;
 	struct s6dp *pd = video_get_drvdata(dev);
-	if (cmd == VIDIOC_ENUMSTD) {
+	struct s6dp_ioctl_config cfg;
+	if (cmd == S6IOCTL_DP_CONFIG) {
+		copy_from_user(&cfg, (void *)arg, sizeof(cfg));
+		pd->cur.modenr = cfg.mode;
+		pd->port = cfg.lane;
+		pd->ext.is_10bit = !!cfg.is_10bit;
+		if ((cfg.micron_mode != (unsigned char)-1)
+		 && (pd->ext.micron != cfg.micron_mode))
+			return -EFAULT;
+		pd->ext.use_1120_line_and_crc = !!cfg.use_1120_line_and_crc;
+		pd->ext.ext_framing = !!cfg.ext_framing;
+		pd->ext.vsync_pol = !!cfg.vsync_pol;
+		pd->ext.hsync_pol = !!cfg.hsync_pol;
+		pd->ext.blank_pol = !!cfg.blank_pol;
+		pd->ext.field_ctrl = !!cfg.field_ctrl;
+		pd->ext.blank_ctrl = !!cfg.blank_ctrl;
+		pd->ext.relaxed_framing_mode = !!cfg.relaxed_framing_mode;
+		pd->ext.desc_size = cfg.desc_size;
+		pd->ext.anc_placement.field[0].pix_start =
+					cfg.anc_placement[0].pix_start;
+		pd->ext.anc_placement.field[0].line_start =
+					cfg.anc_placement[0].line_start;
+		pd->ext.anc_placement.field[0].line_width =
+					cfg.anc_placement[0].line_width;
+		pd->ext.anc_placement.field[0].num_lines =
+					cfg.anc_placement[0].num_lines;
+		pd->ext.anc_placement.field[1].pix_start =
+					cfg.anc_placement[1].pix_start;
+		pd->ext.anc_placement.field[1].line_start =
+					cfg.anc_placement[1].line_start;
+		pd->ext.anc_placement.field[1].line_width =
+					cfg.anc_placement[1].line_width;
+		pd->ext.anc_placement.field[1].num_lines =
+					cfg.anc_placement[1].num_lines;
+		if (cfg.mode == MODE_CUSTOM_VIDEO) {
+			pd->cur.width = cfg.custom.width;
+			pd->cur.height = cfg.custom.height;
+			pd->cur.portsperstream = cfg.custom.portsperstream;
+			pd->cur.greyperchroma = cfg.custom.greyperchroma;
+			pd->cur.progressive = !!cfg.custom.progressive;
+			pd->cur.pixel_total = cfg.custom.reg.pixel_total;
+			pd->cur.pixel_offset = cfg.custom.reg.pixel_offset;
+			pd->cur.pixel_padding = cfg.custom.reg.pixel_padding;
+			pd->cur.line_total = cfg.custom.reg.line_total;
+			pd->cur.line_odd_total = cfg.custom.reg.line_odd_total;
+			pd->cur.line_odd_offset =
+				cfg.custom.reg.line_odd_offset;
+			pd->cur.line_even_offset =
+				cfg.custom.reg.line_even_offset;
+			pd->cur.odd_vsync_len = cfg.custom.reg.odd_vsync_len;
+			pd->cur.odd_vsync_offset =
+				cfg.custom.reg.odd_vsync_offset;
+			pd->cur.even_vsync_len = cfg.custom.reg.even_vsync_len;
+			pd->cur.even_vsync_offset =
+				cfg.custom.reg.even_vsync_offset;
+			pd->cur.odd_hsync_len = cfg.custom.reg.odd_hsync_len;
+			pd->cur.odd_hsync_offset =
+				cfg.custom.reg.odd_hsync_offset;
+			pd->cur.even_hsync_len = cfg.custom.reg.even_hsync_len;
+			pd->cur.even_hsync_offset =
+				cfg.custom.reg.even_hsync_offset;
+			pd->cur.custom = 1;
+		}
+		return 0;
+	} else if (cmd == VIDIOC_ENUMSTD) {
 		struct v4l2_standard std;
 		int ret;
 		if (copy_from_user(&std, (void __user *)arg, sizeof(std)))
@@ -777,23 +1068,34 @@ static inline unsigned s6dp_set_hw2buf(struct s6dp *pd, int chan,
 	return 1 << chan;
 }
 
-static int s6dp_set_current(struct video_device *dev, u32 fourcc, int aligned)
+static int s6dp_set_current(struct video_device *dev, u32 fourcc, u32 modenr,
+			int aligned)
 {
 	struct s6dp *pd = video_get_drvdata(dev);
 	u32 uyl, ayl, uyf, ayf, ucl, acl, acf;
 	pd->cur.fourcc = fourcc;
+	pd->cur.modenr = modenr;
 	pd->cur.aligned = aligned;
-	pd->cur.chansiz[DP_K_OFFSET] = 0;
-	uyl = s6dp_byteperline(pd, 0);
-	ayl = s6dp_bytealigned(uyl);
-	ucl = s6dp_byteperline(pd, 1);
-	acl = s6dp_bytealigned(ucl);
-	uyf = s6dp_byteperframe(pd, 0, uyl);
-	ayf = s6dp_byteperframe(pd, 0, ayl);
-	if (!aligned && ayl != pd->cur.greyperchroma * acl)
-		return -EINVAL;
-	acf = s6dp_byteperframe(pd, 0, acl);
-	switch (fourcc) {
+	if (pd->cur.modenr > MODE_LAST_VIDEO) {
+		u32 s = pd->ext.desc_size;
+		pd->cur.chansiz[DP_Y_OFFSET] = 0;
+		pd->cur.chanoff[DP_CB_OFFSET] = 0;
+		pd->cur.chansiz[DP_CB_OFFSET] = s;
+		pd->cur.chansiz[DP_CR_OFFSET] = 0;
+		pd->cur.chansiz[DP_K_OFFSET] = 0;
+		pd->cur.bufsize = s;
+	} else {
+		pd->cur.chansiz[DP_K_OFFSET] = 0;
+		uyl = s6dp_byteperline(pd, 0);
+		ayl = s6dp_bytealigned(uyl);
+		ucl = s6dp_byteperline(pd, 1);
+		acl = s6dp_bytealigned(ucl);
+		uyf = s6dp_byteperframe(pd, 0, uyl);
+		ayf = s6dp_byteperframe(pd, 0, ayl);
+		if (!aligned && ayl != pd->cur.greyperchroma * acl)
+			return -EINVAL;
+		acf = s6dp_byteperframe(pd, 0, acl);
+		switch (fourcc) {
 	case V4L2_PIX_FMT_YUV444P:
 		if (aligned || uyl == ayl) {
 			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
@@ -812,6 +1114,7 @@ static int s6dp_set_current(struct video_device *dev, u32 fourcc, int aligned)
 		break;
 	default:
 		BUG();
+		}
 	}
 	BUG_ON(pd->cur.bufsize >= (1 << 24));
 	return 0;
@@ -824,6 +1127,8 @@ static int s6v4l_update(struct s6dp *pd, int r)
 
 	if (r < 0)
 		return r;
+	if (pd->cur.custom)
+		return 0;
 	if (!pd->link || !pd->link->g_mode)
 		return -EINVAL; /* no driver, no V4L */
 	pd->link->g_mode(pd->link->context, &mode);
@@ -872,6 +1177,7 @@ static int s6v4l_update(struct s6dp *pd, int r)
 	pd->cur.odd_hsync_offset = mode.hsync_offset / divi;
 	pd->cur.even_hsync_len = mode.hsync_len / divi;
 	pd->cur.even_hsync_offset = mode.hsync_offset / divi;
+	pd->cur.modenr = MODE_LAST_VIDEO;
 	pd->ext.ext_framing = !mode.embedded_sync;
 	pd->ext.micron = mode.micron_mode;
 	pd->ext.vsync_pol = mode.vsync_pol;
@@ -1085,7 +1391,8 @@ static int s6v4l_streamon(struct file *file, void *priv,
 	pd->cur.state = DP_STATE_ACTIVE;
 
 	/* Set the enable bit for the entire DMA group */
-	s6dmac_dp_switch_group(pd->dmac, pd->port, 1);
+	if (pd->cur.modenr <= MODE_LAST_VIDEO || REPEAT_IN_STREAM_MODE)
+		s6dmac_dp_switch_group(pd->dmac, pd->port, 1);
 
 	m = (1 << S6_DP_INT_DMAERR)
 		| (1 << S6_DP_INT_UNDEROVERRUN(pd->port))
@@ -1292,14 +1599,16 @@ static int s6v4l_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
 		pfmt = f->fmt.pix;
 		r = pd->link->s_fmt(pd->link->context, 0, &pfmt, 0);
 	}
-	r = s6v4l_update(pd, r);
+	if (!pd->cur.custom)
+		r = s6v4l_update(pd, r);
 	if (r < 0)
 		return r;
 
 	align = f->fmt.pix.priv & 1;
 	pd->cur.vfield = f->fmt.pix.field;
 	pd->cur.colorspace = f->fmt.pix.colorspace;
-	r = s6dp_set_current(dev, f->fmt.pix.pixelformat, align);
+	r = s6dp_set_current(dev, f->fmt.pix.pixelformat, pd->cur.modenr,
+			     align);
 	return r;
 }
 
-- 
1.6.2.107.ge47ee


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

* [patch 3/5] s6000 data port: canonical modes
  2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
  2009-03-26 14:36 ` [patch 2/5] s6000 data port: custom video mode support Daniel Glöckner
@ 2009-03-26 14:36 ` Daniel Glöckner
  2009-03-26 14:36 ` [patch 4/5] mt9d131 driver for s6000 data port Daniel Glöckner
  2009-03-26 14:36 ` [patch 5/5] saa7121 " Daniel Glöckner
  3 siblings, 0 replies; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Chris Zankel, linux-media, Oskar Schirmer

From: Oskar Schirmer <os@emlix.com>

Add optional handling of a list of video modes not directly supported by
the on-chip video engine. Makes use of extended dma capabilities to provide
these modes:

YUV420 and grey mode as well as planar YUV422 and YUV444 with non-aligned
planes

Signed-off-by: Oskar Schirmer <os@emlix.com>
---
 drivers/media/video/s6dp/Kconfig |    9 +
 drivers/media/video/s6dp/s6dp.c  |  382 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 388 insertions(+), 3 deletions(-)

diff --git a/drivers/media/video/s6dp/Kconfig b/drivers/media/video/s6dp/Kconfig
index 11cc91d..357cfe5 100644
--- a/drivers/media/video/s6dp/Kconfig
+++ b/drivers/media/video/s6dp/Kconfig
@@ -4,3 +4,12 @@ config VIDEO_S6000
 	default n
 	help
 	  Enables the s6000 video driver.
+
+config VIDEO_S6000_CANONICAL
+	tristate "S6000 video canonical modes"
+	depends on VIDEO_S6000
+	default n
+	help
+	  Provides canonical video modes in addition
+	  to the s6 specific ones. You might want these when
+	  standard video software is used with the driver.
diff --git a/drivers/media/video/s6dp/s6dp.c b/drivers/media/video/s6dp/s6dp.c
index 9f349be..68f8e3d 100644
--- a/drivers/media/video/s6dp/s6dp.c
+++ b/drivers/media/video/s6dp/s6dp.c
@@ -60,6 +60,11 @@
 #define CURRENT_BUF_TYPE(pd) ((pd)->ext.egress ? V4L2_BUF_TYPE_VIDEO_OUTPUT \
 					       : V4L2_BUF_TYPE_VIDEO_CAPTURE)
 
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+#define PROCBUFFERS	4
+#define PROCSTEPMAX	3
+#endif
+
 struct s6dp_frame {
 	void *data;
 	dma_addr_t dma_handle;
@@ -67,6 +72,9 @@ struct s6dp_frame {
 	struct list_head list;
 	u32 sequence;
 	u32 flags;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	unsigned procdidx;
+#endif
 };
 
 struct s6dp {
@@ -122,6 +130,26 @@ struct s6dp {
 	struct s6dp_frame *frames;
 	unsigned nrframes;
 	unsigned nrmapped;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	struct {
+		u32 dmac;
+		u8 chan;
+		u8 planemask;
+		u8 stepcnt;
+		u8 maxstep;
+		struct list_head queue;
+		void *buffers_vaddr[PROCBUFFERS];
+		dma_addr_t dma_handle[PROCBUFFERS];
+		u32 bufsize;
+		u8 bufget;
+		u8 bufput;
+		u32 bufoff[PROCSTEPMAX];
+		u32 frameoff[PROCSTEPMAX];
+		s32 stepsize[PROCSTEPMAX];
+		u16 stepchunk[PROCSTEPMAX];
+		u16 stepskip[PROCSTEPMAX];
+	} proc;
+#endif
 	struct {
 		u8 is_10bit:1;
 		u8 micron:1;
@@ -164,6 +192,11 @@ static void s6dp_try_fill_dma(struct s6dp *pd)
 	struct list_head *inq;
 	if (pd->cur.state != DP_STATE_ACTIVE)
 		return;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	if (pd->ext.egress && pd->proc.maxstep)
+		inq = &pd->proc.queue;
+	else
+#endif
 	inq = &pd->idleq;
 	while (!list_empty(inq)) {
 		unsigned chan = pd->port * S6_DP_CHAN_PER_PORT;
@@ -174,10 +207,24 @@ static void s6dp_try_fill_dma(struct s6dp *pd)
 			     && s6dmac_fifo_full(pd->dmac, chan + i))
 				return;
 		f = list_first_entry(inq, struct s6dp_frame, list);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+		if (pd->proc.maxstep) {
+			if (pd->proc.bufget == pd->proc.bufput)
+				return;
+			f->procdidx = (pd->proc.bufput++) % PROCBUFFERS;
+		} else {
+			f->procdidx = 0;
+		}
+#endif
 		list_del(&f->list);
 		list_add_tail(&f->list, &pd->busyq);
 		do if (pd->cur.chansiz[--i]) {
 			u32 h, b, s, d;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+			if ((pd->proc.planemask >> i) & 1)
+				b = (u32)pd->proc.dma_handle[f->procdidx];
+			else
+#endif
 				b = (u32)f->dma_handle;
 			b += pd->cur.chanoff[i];
 			h = pd->dataram + S6_DP_CHAN_OFFSET(i);
@@ -195,6 +242,53 @@ static void s6dp_try_fill_dma(struct s6dp *pd)
 	}
 }
 
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+static void s6dp_try_fill_lms(struct s6dp *pd)
+{
+	if (!list_empty(&pd->proc.queue)) {
+		struct s6dp_frame *f;
+		unsigned n = pd->proc.stepcnt;
+		u32 s;
+		int l;
+		s6dmac_set_stride_skip(pd->proc.dmac, pd->proc.chan,
+			pd->proc.stepchunk[n], pd->proc.stepskip[n], 0);
+		f = list_first_entry(&pd->proc.queue, struct s6dp_frame, list);
+		l = pd->proc.stepsize[n];
+		if (l < 0) {
+			l = -l;
+			s = (u32)pd->proc.dma_handle[f->procdidx];
+		} else {
+			s = (u32)f->dma_handle;
+		}
+		s6dmac_put_fifo(pd->proc.dmac, pd->proc.chan,
+			s + pd->proc.bufoff[n],
+			(u32)f->dma_handle + pd->proc.frameoff[n], l);
+		pd->proc.stepcnt += 1;
+	}
+}
+
+static void s6dp_lms_interrupt(struct s6dp *pd)
+{
+	struct s6dp_frame *f;
+	if (pd->cur.state != DP_STATE_ACTIVE)
+		return;
+	if (pd->proc.stepcnt == pd->proc.maxstep) {
+		if (list_empty(&pd->proc.queue)) {
+			printk(DRV_ERR "no buffers available in processing\n");
+			return;
+		}
+		f = list_first_entry(&pd->proc.queue, struct s6dp_frame, list);
+		list_del(&f->list);
+		pd->proc.stepcnt = 0;
+		pd->proc.bufget += 1;
+		list_add_tail(&f->list, &pd->fullq);
+		wake_up_interruptible(&pd->wait);
+	}
+	s6dp_try_fill_lms(pd);
+	s6dp_try_fill_dma(pd);
+}
+#endif
+
 static void s6dp_err_interrupt(struct s6dp *pd)
 {
 	u32 m, r = DP_REG_R(pd, S6_DP_INT_UNMAP_RAW1);
@@ -250,6 +344,10 @@ static void s6dp_tc_interrupt(struct s6dp *pd)
 		u32 newfc, pending, global;
 		struct list_head *outq = &pd->fullq;
 
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+		if (!pd->ext.egress && pd->proc.maxstep)
+			outq = &pd->proc.queue;
+#endif
 		do_gettimeofday(&now);
 		global = readl(S6_REG_GREG1 + S6_GREG1_GLOBAL_TIMER);
 		DP_REG_W(pd, S6_DP_INT_CLEAR, 1 << S6_DP_INT_TERMCNT(pd->port));
@@ -286,6 +384,10 @@ static void s6dp_tc_interrupt(struct s6dp *pd)
 		if (unlikely(list_empty(&pd->busyq)) && pending)
 			printk(DRV_ERR "no repeating frame?\n");
 		s6dp_try_fill_dma(pd);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+		if (!list_empty(&pd->proc.queue) && !pd->proc.stepcnt)
+			s6dp_try_fill_lms(pd);
+#endif
 		if (!list_empty(&pd->fullq))
 			wake_up_interruptible(&pd->wait);
 	}
@@ -311,6 +413,14 @@ static irqreturn_t s6dp_interrupt(int irq, void *dev_id)
 				s6dp_tc_interrupt(pd);
 				ret = IRQ_HANDLED;
 			}
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+			if (s6dmac_pendcnt_irq(pd->proc.dmac, pd->proc.chan)
+			     && !s6dmac_pending_count(pd->proc.dmac,
+						      pd->proc.chan)) {
+				s6dp_lms_interrupt(pd);
+				ret = IRQ_HANDLED;
+			}
+#endif
 			spin_unlock(&pd->lock);
 		}
 	}
@@ -395,6 +505,17 @@ static int s6dp_dma_init(struct video_device *dev)
 		& ~(7 << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)))
 		| (burstsize << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)));
 
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	if (pd->proc.maxstep) {
+		n = s6dmac_request_chan(pd->proc.dmac, pd->proc.chan, 1,
+			-1, 1, 1, 0, 0, 0, 7, -1, 1, 0, 1);
+		if (n < 0) {
+			printk(DRV_ERR "error - LMS DMA not available\n");
+			goto errdma;
+		}
+		pd->proc.stepcnt = 0;
+	}
+#endif
 	return 0;
 errdma:
 	while (i > 0) {
@@ -419,6 +540,12 @@ static void s6dp_dma_free(struct video_device *dev)
 		s6dmac_release_chan(pd->dmac,
 				    pd->port * S6_DP_CHAN_PER_PORT + i);
 	} while (++i < n);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	if (pd->proc.maxstep) {
+		s6dmac_release_chan(pd->proc.dmac, pd->proc.chan);
+		pd->proc.stepcnt = 0;
+	}
+#endif
 }
 
 static int s6dp_setup_stream(struct video_device *dev)
@@ -855,12 +982,24 @@ static void s6dp_relbufs(struct video_device *dev)
 	INIT_LIST_HEAD(&pd->idleq);
 	INIT_LIST_HEAD(&pd->busyq);
 	INIT_LIST_HEAD(&pd->fullq);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	INIT_LIST_HEAD(&pd->proc.queue);
+#endif
 	spin_unlock_irqrestore(&pd->lock, flags);
 	for (i = 0; i < pd->nrframes; i++)
 		dma_free_coherent(dev->parent, pd->cur.bufsize,
 				  pd->frames[i].data, pd->frames[i].dma_handle);
 	kfree(pd->frames);
 	pd->nrframes = 0;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	if (pd->proc.bufsize) {
+		for (i = 0; i < PROCBUFFERS; i++)
+			dma_free_noncoherent(dev->parent, pd->proc.bufsize,
+					     pd->proc.buffers_vaddr[i],
+					     pd->proc.dma_handle[i]);
+		pd->proc.bufsize = 0;
+	}
+#endif
 }
 
 static int s6dp_video_close(struct file *file)
@@ -1068,11 +1207,41 @@ static inline unsigned s6dp_set_hw2buf(struct s6dp *pd, int chan,
 	return 1 << chan;
 }
 
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+static inline int s6dp_set_procbuf(struct s6dp *pd, unsigned count,
+			unsigned srcoff, unsigned trgoff, unsigned size,
+			unsigned srcseek, unsigned trgseek,
+			unsigned srcchunk, unsigned trgchunk)
+{
+	BUG_ON(count >= PROCSTEPMAX);
+	pd->proc.bufoff[count] = srcoff + srcseek;
+	pd->proc.frameoff[count] = trgoff + trgseek;
+	pd->proc.stepsize[count] = size - trgseek;
+	pd->proc.stepchunk[count] = trgchunk;
+	pd->proc.stepskip[count] = srcchunk - trgchunk;
+	return 1;
+}
+
+static inline int s6dp_set_proctmp(struct s6dp *pd, unsigned count,
+			unsigned srcoff, unsigned trgoff, signed size,
+			unsigned srcchunk, unsigned trgchunk)
+{
+	return s6dp_set_procbuf(pd, count, srcoff, trgoff, -size,
+			0, 0, srcchunk, trgchunk);
+}
+#endif
+
 static int s6dp_set_current(struct video_device *dev, u32 fourcc, u32 modenr,
 			int aligned)
 {
 	struct s6dp *pd = video_get_drvdata(dev);
 	u32 uyl, ayl, uyf, ayf, ucl, acl, acf;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	u32 t, ucf;
+	t = 0;
+	pd->proc.planemask = 0;
+	pd->proc.maxstep = 0;
+#endif
 	pd->cur.fourcc = fourcc;
 	pd->cur.modenr = modenr;
 	pd->cur.aligned = aligned;
@@ -1092,8 +1261,13 @@ static int s6dp_set_current(struct video_device *dev, u32 fourcc, u32 modenr,
 		acl = s6dp_bytealigned(ucl);
 		uyf = s6dp_byteperframe(pd, 0, uyl);
 		ayf = s6dp_byteperframe(pd, 0, ayl);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+		ucf = s6dp_byteperframe(pd, fourcc == V4L2_PIX_FMT_YUV420,
+				aligned ? acl : ucl);
+#else
 		if (!aligned && ayl != pd->cur.greyperchroma * acl)
 			return -EINVAL;
+#endif
 		acf = s6dp_byteperframe(pd, 0, acl);
 		switch (fourcc) {
 	case V4L2_PIX_FMT_YUV444P:
@@ -1102,6 +1276,20 @@ static int s6dp_set_current(struct video_device *dev, u32 fourcc, u32 modenr,
 			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
 			s6dp_set_hw2buf(pd, DP_CR_OFFSET, ayf + acf, acf);
 			pd->cur.bufsize = ayf + 2 * acf;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+		} else {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, 0, acf);
+			pd->proc.maxstep =
+				s6dp_set_procbuf(pd, 0, 0, 0, uyf + ucf,
+					ayl, uyl, acl, ucl) +
+				s6dp_set_proctmp(pd, 1, 0, uyf + ucf, ucf,
+						acl, ucl);
+			pd->cur.bufsize = uyf + 2 * ucf;
+			t = acf;
+#endif
 		}
 		break;
 	case V4L2_PIX_FMT_YUV422P:
@@ -1110,13 +1298,132 @@ static int s6dp_set_current(struct video_device *dev, u32 fourcc, u32 modenr,
 			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
 			s6dp_set_hw2buf(pd, DP_CR_OFFSET, ayf + acf, acf);
 			pd->cur.bufsize = ayf + 2 * acf;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+		} else if (uyl == ayl) {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, 0, acf);
+			pd->proc.maxstep =
+				s6dp_set_procbuf(pd, 0, ayf, uyf, ucf,
+					acl, ucl, acl, ucl) +
+				s6dp_set_proctmp(pd, 1, 0, uyf + ucf, ucf,
+						acl, ucl);
+			pd->cur.bufsize = uyf + 2 * ucf;
+			t = acf;
+		} else {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, 0, acf);
+			pd->proc.maxstep =
+				s6dp_set_procbuf(pd, 0, 0, 0, uyf,
+					ayl, uyl, ayl, uyl) +
+				s6dp_set_procbuf(pd, 1, ayf, uyf, ucf,
+					0, 0, acl, ucl) +
+				s6dp_set_proctmp(pd, 2, 0, uyf + ucf, ucf,
+						acl, ucl);
+			pd->cur.bufsize = uyf + 2 * ucf;
+			t = acf;
+#endif
 		}
 		break;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	case V4L2_PIX_FMT_YUV420:
+		if (aligned || ucl == acl) {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, 0, acf);
+			pd->proc.maxstep =
+				s6dp_set_procbuf(pd, 0, ayf, ayf, ucf,
+					2*acl, acl, 2*acl, acl) +
+				s6dp_set_proctmp(pd, 1, 0, ayf + ucf, ucf,
+						2*acl, acl);
+			pd->cur.bufsize = ayf + 2 * ucf;
+			t = acf;
+		} else if (uyl == ayl) {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CB_OFFSET, 0, acf) +
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, acf, acf);
+			pd->proc.maxstep =
+				s6dp_set_proctmp(pd, 0, 0, uyf, 2*ucf,
+						2*acl, ucl);
+			pd->cur.bufsize = uyf + 2 * ucf;
+			t = 2*acf;
+		} else {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CB_OFFSET, 0, acf) +
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, acf, acf);
+			pd->proc.maxstep =
+				s6dp_set_procbuf(pd, 0, 0, 0, uyf,
+					ayl, uyl, ayl, uyl) +
+				s6dp_set_proctmp(pd, 1, 0, uyf, 2*ucf,
+						2*acl, ucl);
+			pd->cur.bufsize = uyf + 2 * ucf;
+			t = 2*acf;
+		}
+		break;
+	case V4L2_PIX_FMT_GREY:
+		if (aligned || uyl == ayl) {
+			s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_CB_OFFSET, 0, acf) +
+				s6dp_set_hw2buf(pd, DP_CR_OFFSET, 0, acf);
+			pd->cur.bufsize = ayf;
+			t = acf;
+		} else {
+			s6dp_set_hw2buf(pd, DP_CB_OFFSET, 0, acf);
+			s6dp_set_hw2buf(pd, DP_CR_OFFSET, 0, acf);
+			pd->proc.planemask =
+				s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+			pd->proc.maxstep =
+				s6dp_set_proctmp(pd, 0, 0, 0, uyf, ayl, uyl);
+			pd->cur.bufsize = uyf;
+			t = ayf;
+		}
+		break;
+#endif
 	default:
 		BUG();
 		}
 	}
 	BUG_ON(pd->cur.bufsize >= (1 << 24));
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	if (pd->proc.bufsize) {
+		int i;
+		for (i = 0; i < PROCBUFFERS; i++)
+			dma_free_noncoherent(dev->parent, pd->proc.bufsize,
+					     pd->proc.buffers_vaddr[i],
+					     pd->proc.dma_handle[i]);
+		pd->proc.bufsize = 0;
+	}
+	if (t) {
+		int i;
+		for (i = 0; i < PROCBUFFERS; i++) {
+			void *p;
+			p = dma_alloc_noncoherent(dev->parent, t,
+						  pd->proc.dma_handle + i,
+						  GFP_KERNEL);
+			if (!p)
+				break;
+			pd->proc.buffers_vaddr[i] = p;
+		}
+		if (i < PROCBUFFERS) {
+			while (i--)
+				dma_free_noncoherent(dev->parent,
+						     pd->proc.bufsize,
+						     pd->proc.buffers_vaddr[i],
+						     pd->proc.dma_handle[i]);
+			return -ENOMEM;
+		}
+		pd->proc.bufsize = t;
+		pd->proc.bufget = PROCBUFFERS;
+		pd->proc.bufput = 0;
+	}
+#endif
 	return 0;
 }
 
@@ -1434,6 +1741,14 @@ const static struct {
 	{	V4L2_PIX_FMT_YUV422P,
 		"YUV 4:2:2 planar",
 	},
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	{	V4L2_PIX_FMT_YUV420,
+		"YUV 4:2:0 planar",
+	},
+	{	V4L2_PIX_FMT_GREY,
+		"GREY",
+	},
+#endif
 };
 
 static int s6v4l_enum_fmt_cap(struct file *file, void *priv,
@@ -1515,6 +1830,9 @@ static int s6v4l_try_fmt(struct file *file, void *priv,
 	struct video_device *dev = file->private_data;
 	struct s6dp *pd = video_get_drvdata(dev);
 	int cwidth, cheight, cbytesperline, aligned = 1;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	u32 reqfourcc = f->fmt.pix.pixelformat;
+#endif
 	if (!pd->link || !pd->link->s_fmt || !pd->link->g_mode)
 		return 0;
 
@@ -1533,6 +1851,23 @@ static int s6v4l_try_fmt(struct file *file, void *priv,
 	}
 	if (f->fmt.pix.field == V4L2_FIELD_ALTERNATE)
 		f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	switch (f->fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_YUV422P:
+		switch (reqfourcc) {
+		case V4L2_PIX_FMT_YUV420:
+		case V4L2_PIX_FMT_GREY:
+			f->fmt.pix.pixelformat = reqfourcc;
+		}
+		break;
+	case V4L2_PIX_FMT_YUV444P:
+		switch (reqfourcc) {
+		case V4L2_PIX_FMT_GREY:
+			f->fmt.pix.pixelformat = reqfourcc;
+		}
+	}
+	aligned = f->fmt.pix.priv & 1;
+#endif
 	cheight = f->fmt.pix.height;
 	switch (f->fmt.pix.pixelformat) {
 	case V4L2_PIX_FMT_YUV444P:
@@ -1579,6 +1914,10 @@ static int s6v4l_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
 	f->fmt.pix.height = pd->cur.height;
 	f->fmt.pix.pixelformat = pd->cur.fourcc;
 	f->fmt.pix.bytesperline = s6dp_bytealigned(f->fmt.pix.width);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	if (!pd->cur.aligned)
+		f->fmt.pix.bytesperline = f->fmt.pix.width;
+#endif
 	f->fmt.pix.priv = pd->cur.aligned;
 	f->fmt.pix.sizeimage = pd->cur.bufsize;
 	return 0;
@@ -1764,7 +2103,8 @@ static const struct v4l2_ioctl_ops output_v4l_ioctl_ops = {
 
 static int probe_one(struct platform_device *pdev, int irq,
 		     struct video_device **devs, struct s6dp_link *link,
-		     void __iomem *dpbase, void __iomem *dmac, u32 physbase)
+		     void __iomem *dpbase, void __iomem *dmac, u32 physbase,
+		     void __iomem *procdmac, int instance)
 {
 	struct video_device *dev;
 	struct s6dp *pd;
@@ -1794,6 +2134,10 @@ static int probe_one(struct platform_device *pdev, int irq,
 	pd->irq = irq;
 	pd->dp = dpbase;
 	pd->dmac = (u32)dmac;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	pd->proc.dmac = DMA_MASK_DMAC((u32)procdmac);
+	pd->proc.chan = DMA_INDEX_CHNL((u32)procdmac) + instance;
+#endif
 	for (index = 0; !(link->port_mask & (1 << index)); index++)
 		;
 	if (link->port_mask != (1 << index)) {
@@ -1809,6 +2153,9 @@ static int probe_one(struct platform_device *pdev, int irq,
 	INIT_LIST_HEAD(&pd->idleq);
 	INIT_LIST_HEAD(&pd->busyq);
 	INIT_LIST_HEAD(&pd->fullq);
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	INIT_LIST_HEAD(&pd->proc.queue);
+#endif
 	init_waitqueue_head(&pd->wait);
 	spin_lock_init(&pd->lock);
 	if (video_register_device_index(dev, VFL_TYPE_GRABBER, link->minor,
@@ -1835,8 +2182,11 @@ static int __devinit s6dp_probe(struct platform_device *pdev)
 	unsigned in_use;
 	struct video_device **devs;
 	struct s6dp_link *links;
-	void __iomem *dpbase, *dmacbase;
+	void __iomem *dpbase, *dmacbase, *procdmacbase = 0;
 	struct resource *res, *regs, *dmac;
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	struct resource *procdmac;
+#endif
 	if (!pdev->dev.platform_data) {
 		printk(DRV_ERR "no platform data given\n");
 		return -EINVAL;
@@ -1884,6 +2234,25 @@ static int __devinit s6dp_probe(struct platform_device *pdev)
 		ret = -ENOMEM;
 		goto err_free_dmac;
 	}
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!res) {
+		ret = -EINVAL;
+		goto err_unmap_dmac;
+	}
+	procdmac = request_mem_region(res->start, res->end - res->start + 1,
+				      pdev->name);
+	if (!procdmac) {
+		ret = -EBUSY;
+		goto err_unmap_dmac;
+	}
+	procdmacbase = ioremap_nocache(procdmac->start,
+				       procdmac->end - procdmac->start + 1);
+	if (!procdmacbase) {
+		ret = -ENOMEM;
+		goto err_free_procdmac;
+	}
+#endif
 	i = 0;
 	in_use = 0;
 	for (links = pdev->dev.platform_data; links->port_mask; links++) {
@@ -1892,7 +2261,7 @@ static int __devinit s6dp_probe(struct platform_device *pdev)
 			continue;
 		}
 		ret = probe_one(pdev, irq, &devs[i], links, dpbase, dmacbase,
-				regs->start);
+				regs->start, procdmacbase, i);
 		if (ret)
 			goto err_free_devs;
 		in_use |= links->port_mask;
@@ -1910,6 +2279,13 @@ err_free_devs:
 			video_device_release(devs[i]);
 		}
 	}
+#ifdef CONFIG_VIDEO_S6000_CANONICAL
+	iounmap(procdmacbase);
+err_free_procdmac:
+	release_mem_region(procdmac->start,
+			   procdmac->end - procdmac->start + 1);
+err_unmap_dmac:
+#endif
 	iounmap(dmacbase);
 err_free_dmac:
 	release_mem_region(dmac->start, dmac->end - dmac->start + 1);
-- 
1.6.2.107.ge47ee


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

* [patch 4/5] mt9d131 driver for s6000 data port
  2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
  2009-03-26 14:36 ` [patch 2/5] s6000 data port: custom video mode support Daniel Glöckner
  2009-03-26 14:36 ` [patch 3/5] s6000 data port: canonical modes Daniel Glöckner
@ 2009-03-26 14:36 ` Daniel Glöckner
  2009-03-26 14:36 ` [patch 5/5] saa7121 " Daniel Glöckner
  3 siblings, 0 replies; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Chris Zankel, linux-media, Daniel Glöckner

This patch adds a driver to support the mt9d131 camera in combination
with the s6000 data port driver.

Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
 drivers/media/video/s6dp/Kconfig        |    7 +
 drivers/media/video/s6dp/Makefile       |    1 +
 drivers/media/video/s6dp/s6dp-mt9d131.c | 1051 +++++++++++++++++++++++++++++++
 3 files changed, 1059 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/video/s6dp/s6dp-mt9d131.c

diff --git a/drivers/media/video/s6dp/Kconfig b/drivers/media/video/s6dp/Kconfig
index 357cfe5..853e6b1 100644
--- a/drivers/media/video/s6dp/Kconfig
+++ b/drivers/media/video/s6dp/Kconfig
@@ -13,3 +13,10 @@ config VIDEO_S6000_CANONICAL
 	  Provides canonical video modes in addition
 	  to the s6 specific ones. You might want these when
 	  standard video software is used with the driver.
+
+config VIDEO_S6DP_MT9D131
+	tristate "MT9D131 camera"
+	depends on VIDEO_S6000
+	default n
+	help
+	  Enables the MT9D131 camera driver.
diff --git a/drivers/media/video/s6dp/Makefile b/drivers/media/video/s6dp/Makefile
index c503d5b..af0bc0f 100644
--- a/drivers/media/video/s6dp/Makefile
+++ b/drivers/media/video/s6dp/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_VIDEO_S6000) += s6dp.o
+obj-$(CONFIG_VIDEO_S6DP_MT9D131) += s6dp-mt9d131.o
diff --git a/drivers/media/video/s6dp/s6dp-mt9d131.c b/drivers/media/video/s6dp/s6dp-mt9d131.c
new file mode 100644
index 0000000..954e8e0
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp-mt9d131.c
@@ -0,0 +1,1051 @@
+/*
+ * Micron Camera driver
+ * (c)2007 Stretch, Inc.
+ * (c)2008 emlix GmbH <info@emlix.com>
+ * Authors:	Fabian Godehardt <fg@emlix.com>
+ *		Oskar Schirmer <os@emlix.com>
+ *		Daniel Glöckner <dg@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/time.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/stddef.h>
+#include <linux/videodev2.h>
+
+#include <media/s6dp-link.h>
+#include "s6dp.h"
+
+#define PFX "mt9d131: "
+
+#define MT9D131_REG_SENSOR 0 /* direct access (Table 5) */
+#define MT9D131_REG_IFP1 1 /* direct access (Table 6) */
+#define MT9D131_REG_IFP2 2 /* direct access (Table 7) */
+#define MT9D131_REG_JPEG 3 /* indirect access, IFP2 reg 30/31 (Table 8) */
+#define MT9D131_REG8_UPROC 4 /* indirect access, IFP1 reg 0xc6/0xc8 (8 bits) */
+#define MT9D131_REG8_UPROC_seq 5 /* indirect, IFP1 reg 0xc6/0xc8 (Table 10) */
+#define MT9D131_REG8_UPROC_ae 6 /* indirect, IFP1 reg 0xc6/0xc8 (Table 11) */
+#define MT9D131_REG8_UPROC_wb 7 /* indirect, IFP1 reg 0xc6/0xc8 (Table 11) */
+#define MT9D131_REG8_UPROC_mode 11 /* indirect, IFP1 reg 0xc6/0xc8 (Table 12) */
+#define MT9D131_REG8_UPROC_jpeg 13 /* indirect, IFP1 reg 0xc6/0xc8 (Table 13) */
+#define MT9D131_REG16_UPROC 24 /* indirect, IFP1 reg 0xc6/0xc8 (16 bits) */
+#define MT9D131_REG16_UPROC_seq 25
+#define MT9D131_REG16_UPROC_ae 26
+#define MT9D131_REG16_UPROC_wb 27
+#define MT9D131_REG16_UPROC_mode 31
+#define MT9D131_REG16_UPROC_jpeg 33
+
+#define MT9D131_CONTEXT_A	0
+#define MT9D131_CONTEXT_B	1
+#define MT9D131_NB_CONTEXTS	2
+
+#define MT9D131_CORE_ROWSTART		0x01
+#define MT9D131_CORE_COLSTART		0x02
+#define MT9D131_CORE_ROWWIDTH		0x03
+#define MT9D131_CORE_COLWIDTH		0x04
+#define MT9D131_CORE_HBLANKB		0x05
+#define MT9D131_CORE_VBLANKB		0x06
+#define MT9D131_CORE_HBLANKA		0x07
+#define MT9D131_CORE_VBLANKA		0x08
+#define MT9D131_CORE_SHUTTERWIDTH	0x09
+#define MT9D131_CORE_ROWSPEED		0x0A
+#define MT9D131_CORE_READMODEB		0x20
+#define MT9D131_CORE_READMODEA		0x21
+#define MT9D131_CORE_GREEN1GAIN		0x2B
+#define MT9D131_CORE_BLUEGAIN		0x2C
+#define MT9D131_CORE_REDGAIN		0x2D
+#define MT9D131_CORE_GREEN2GAIN		0x2E
+#define MT9D131_CORE_CLKCTRL		0x65
+#define MT9D131_CORE_PLLCTRL1		0x66
+#define MT9D131_CORE_PLLCTRL2		0x67
+
+#define MT9D131_IFP1_CROPWINDOWX0	0x11
+#define MT9D131_IFP1_CROPWINDOWX1	0x12
+#define MT9D131_IFP1_CROPWINDOWY0	0x13
+#define MT9D131_IFP1_CROPWINDOWY1	0x14
+#define MT9D131_IFP1_HORZDECIMATIONWGT	0x16
+#define MT9D131_IFP1_VERTDECIMATIONWGT	0x17
+#define MT9D131_IFP1_OUTPUTFMTCONFIG	0x97
+#define MT9D131_IFP1_YUVCTRL		0xBE
+#define MT9D131_IFP1_YRGBOFFSET		0xBF
+#define MT9D131_IFP1_UPROCVARADDR	0xC6
+#define MT9D131_IFP1_UPROCVARADDR_ADDR		0
+#define MT9D131_IFP1_UPROCVARADDR_ADDR_MASK		0xFF
+#define MT9D131_IFP1_UPROCVARADDR_DRVID		8
+#define MT9D131_IFP1_UPROCVARADDR_DRVID_MASK		0x1F
+#define MT9D131_IFP1_UPROCVARADDR_LOGICAL	13
+#define MT9D131_IFP1_UPROCVARADDR_BYTEWISE	15
+#define MT9D131_IFP1_UPROCVARADDR_JPEG		0
+#define MT9D131_IFP1_UPROCVARADDR_JPEG_MASK		0x7FF
+#define MT9D131_IFP1_UPROCVARADDR_I2CBURST	13
+#define MT9D131_IFP1_UPROCVARADDR_WRITE		14
+#define MT9D131_IFP1_UPROCVARADDR_AUTOINCR	15
+#define MT9D131_IFP1_UPROCVARDATA	0xC8
+
+#define MT9D131_IFP2_JPEGINDIRECTDATA	0x1F
+
+#define MT9D131_COMMON_PAGEREGISTER	0xF0
+#define MT9D131_COMMON_BYTEWISEADDR	0xF1
+
+#define MT9D131_MAXSENSOR_WIDTH		1600
+#define MT9D131_MAXSENSOR_HEIGHT	1200
+
+#define MT9D131_VBLANK_MIN		11
+#define MT9D131_HBLANK_MIN		286
+
+struct mt9d131_settings {
+	/* sensor/crop/output geometries */
+	unsigned sensor_w, sensor_h; /* height/width of sensor area read-out */
+	unsigned sensor_x0, sensor_y0; /* informational only */
+	unsigned crop_w, crop_h; /* crop window, height and width */
+	unsigned crop_x0, crop_y0; /* informational only */
+	unsigned output_w, output_h; /* output window, height and width */
+	unsigned std_num;
+	enum v4l2_colorspace colorspace;
+};
+
+struct mt9d131_device {
+	int current_reg_set;
+	struct mt9d131_settings current_settings;
+};
+
+static int raw_read_register(struct i2c_client *client, u8 reg_addr,
+			     u16 *ret_data)
+{
+	s32 ret;
+	ret = i2c_smbus_read_word_data(client, reg_addr);
+	if (ret < 0)
+		return ret;
+	*ret_data = swab16(ret);
+	return 0;
+}
+
+static int raw_write_register(struct i2c_client *client, u8 reg_addr,
+			      u16 write_data)
+{
+	return i2c_smbus_write_word_data(client, reg_addr, swab16(write_data));
+}
+
+static inline void mt9d131_switch_page(struct i2c_client *client, int reg_set)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	if (dev->current_reg_set != reg_set) {
+		raw_write_register(client, MT9D131_COMMON_PAGEREGISTER,
+				   reg_set);
+		dev->current_reg_set = reg_set;
+	}
+}
+
+static int mt9d131_write_register(struct i2c_client *client, int reg_set,
+	u32 reg_offset, u16 wdata)
+{
+	if (reg_set >= MT9D131_REG8_UPROC) {
+		mt9d131_switch_page(client, MT9D131_REG_IFP1);
+		if (reg_set >= MT9D131_REG16_UPROC)
+			raw_write_register(client, MT9D131_IFP1_UPROCVARADDR,
+				((reg_offset
+					& MT9D131_IFP1_UPROCVARADDR_ADDR_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_ADDR) |
+				(((reg_set - MT9D131_REG16_UPROC)
+					& MT9D131_IFP1_UPROCVARADDR_DRVID_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_DRVID) |
+				(1 << MT9D131_IFP1_UPROCVARADDR_LOGICAL) |
+				(0 << MT9D131_IFP1_UPROCVARADDR_BYTEWISE));
+		else
+			raw_write_register(client, MT9D131_IFP1_UPROCVARADDR,
+				((reg_offset
+					& MT9D131_IFP1_UPROCVARADDR_ADDR_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_ADDR) |
+				(((reg_set - MT9D131_REG8_UPROC)
+					& MT9D131_IFP1_UPROCVARADDR_DRVID_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_DRVID) |
+				(1 << MT9D131_IFP1_UPROCVARADDR_LOGICAL) |
+				(1 << MT9D131_IFP1_UPROCVARADDR_BYTEWISE));
+		return raw_write_register(client, MT9D131_IFP1_UPROCVARDATA,
+			wdata);
+	}
+	if (reg_set == MT9D131_REG_JPEG) {
+		BUG(); /* broken? */
+		mt9d131_switch_page(client, MT9D131_REG_IFP2);
+		raw_write_register(client, MT9D131_IFP2_JPEGINDIRECTDATA,
+			wdata);
+		return raw_write_register(client, MT9D131_IFP1_UPROCVARADDR,
+			((reg_offset
+				& MT9D131_IFP1_UPROCVARADDR_JPEG_MASK)
+				<< MT9D131_IFP1_UPROCVARADDR_JPEG) |
+			(0 << MT9D131_IFP1_UPROCVARADDR_I2CBURST) |
+			(1 << MT9D131_IFP1_UPROCVARADDR_WRITE) |
+			(0 << MT9D131_IFP1_UPROCVARADDR_AUTOINCR));
+	}
+	mt9d131_switch_page(client, reg_set);
+	return raw_write_register(client, reg_offset, wdata);
+}
+
+static int mt9d131_read_register(struct i2c_client *client, int reg_set,
+	u32 reg_offset, u16 *rdata)
+{
+	if (!rdata)
+		return -EINVAL;
+	*rdata = 0;
+	if (reg_set >= MT9D131_REG8_UPROC) {
+		mt9d131_switch_page(client, MT9D131_REG_IFP1);
+		if (reg_set >= MT9D131_REG16_UPROC)
+			raw_write_register(client, MT9D131_IFP1_UPROCVARADDR,
+				((reg_offset
+					& MT9D131_IFP1_UPROCVARADDR_ADDR_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_ADDR) |
+				(((reg_set - MT9D131_REG16_UPROC)
+					& MT9D131_IFP1_UPROCVARADDR_DRVID_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_DRVID) |
+				(1 << MT9D131_IFP1_UPROCVARADDR_LOGICAL) |
+				(0 << MT9D131_IFP1_UPROCVARADDR_BYTEWISE));
+		else
+			raw_write_register(client, MT9D131_IFP1_UPROCVARADDR,
+				((reg_offset
+					& MT9D131_IFP1_UPROCVARADDR_ADDR_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_ADDR) |
+				(((reg_set - MT9D131_REG8_UPROC)
+					& MT9D131_IFP1_UPROCVARADDR_DRVID_MASK)
+					<< MT9D131_IFP1_UPROCVARADDR_DRVID) |
+				(1 << MT9D131_IFP1_UPROCVARADDR_LOGICAL) |
+				(1 << MT9D131_IFP1_UPROCVARADDR_BYTEWISE));
+		return raw_read_register(client, MT9D131_IFP1_UPROCVARDATA,
+			rdata);
+	}
+
+	if (reg_set == MT9D131_REG_JPEG) {
+		BUG(); /* broken? */
+		mt9d131_switch_page(client, MT9D131_REG_IFP2);
+		raw_write_register(client, MT9D131_IFP1_UPROCVARADDR,
+			((reg_offset
+				& MT9D131_IFP1_UPROCVARADDR_JPEG_MASK)
+				<< MT9D131_IFP1_UPROCVARADDR_JPEG) |
+			(0 << MT9D131_IFP1_UPROCVARADDR_I2CBURST) |
+			(0 << MT9D131_IFP1_UPROCVARADDR_WRITE) |
+			(0 << MT9D131_IFP1_UPROCVARADDR_AUTOINCR));
+		return raw_read_register(client, MT9D131_IFP2_JPEGINDIRECTDATA,
+			rdata);
+	}
+	mt9d131_switch_page(client, reg_set);
+	return raw_read_register(client, reg_offset, rdata);
+}
+
+/**
+    Setup the PLL inside MT9D131
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+	"extclk_freq" - The frequency of the clock at the EXTCLK input.
+	"desired_fout_freq" - The desired output frequency
+ */
+static int mt9d131_setup_pll(struct i2c_client *client,
+		u32 extclk_freq, u32 desired_fout_freq)
+{
+	u32 div;
+	u32 N, M, P;
+	u32 f_vco;
+	u16 reg16;
+
+	if (desired_fout_freq > 80 || desired_fout_freq < 6)
+		return -EINVAL;
+
+	if (extclk_freq > 64 || extclk_freq < 6)
+		return -EINVAL;
+
+	/* the VCO center is 175MHz. Aim for that. */
+	div = 175/desired_fout_freq;
+	P = div/2 - 1;
+	f_vco = desired_fout_freq * 2*(P+1);
+
+	/*
+	 * Calculate M and N, subject to:
+	 *     f_in       N+1
+	 *     ----   =  -----
+	 *     f_vco       M
+	 */
+	M = f_vco;
+	N = extclk_freq;
+
+	while (!(N % 2) && !(M % 2) && M >= 16*2) {
+		N /= 2;
+		M /= 2;
+	}
+	while (!(N % 3) && !(M % 3) && M >= 16*3) {
+		N /= 3;
+		M /= 3;
+	}
+	N--;
+
+	/*
+	 * Now that all the parameters are determined, program the PLL and
+	 * power it up.
+	 */
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_PLLCTRL1, M<<8 | N);
+	mt9d131_read_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_PLLCTRL2, &reg16);
+	reg16 &= ~0x7f; /* bits[6:0] = P */
+	reg16 |= (P & 0x7f);
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_PLLCTRL2, reg16);
+
+	/* power up the PLL */
+	mt9d131_read_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_CLKCTRL, &reg16);
+	reg16 &= ~(1<<14); /* powerdown bit */
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_CLKCTRL, reg16);
+
+	/* wait for it to lock and settle */
+	mdelay(1);
+	udelay(150);
+
+	/* turn off PLL bypass */
+	reg16 &= ~(1<<15); /* bypass bit */
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_CLKCTRL, reg16);
+	return 0;
+}
+
+/*******************************************************************************
+    Set the size and position of the cropping window, and the size of the output
+    window.  The size of the cropping window must be larger than or equal to the
+    size of the output window.  It also must be smaller than or equal to the
+    size of the sensor FOV (set by sx_mt9d131_set_sensor_fov)
+
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+	"crop_x0"
+	"crop_y0" - The upper left corner of the crop window
+	"crop_width_x"
+	"crop_width_y" - The size of the crop window
+	"out_width_x"
+	"out_width_y" - The size of the crop window
+*******************************************************************************/
+static int mt9d131_set_window(struct i2c_client *client,
+			u32 crop_x0, u32 crop_y0,
+			u32 crop_width_x, u32 crop_height_y,
+			u32 out_width_x, u32 out_height_y)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	int context;
+
+	if ((crop_width_x < out_width_x) || (crop_height_y < out_height_y))
+		return -EINVAL;
+
+	if (((crop_width_x+crop_x0)  > dev->current_settings.sensor_w) ||
+	    ((crop_height_y+crop_y0) > dev->current_settings.sensor_h))
+		return -EINVAL;
+
+	/*
+	 * save the crop/output window size and position into the
+	 * current settings.
+	 */
+	dev->current_settings.crop_w  = crop_width_x;
+	dev->current_settings.crop_h  = crop_height_y;
+	dev->current_settings.crop_x0 = crop_x0;
+	dev->current_settings.crop_y0 = crop_y0;
+	dev->current_settings.output_w = out_width_x;
+	dev->current_settings.output_h = out_height_y;
+
+	/*
+	 * upload the settings into both context A and B.
+	 * This ensures that switches between the two (required when FOV
+	 * is changed). Do not cause output dimension glitchs.
+	 * This function wants to assume that it is called while
+	 * the sensor is in context B. Therefore, it should be
+	 * able to update the crop/output windows for A.
+	 */
+	for (context = MT9D131_CONTEXT_B;
+	     context <= MT9D131_CONTEXT_B;
+	     context++) {
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			5+4*context, /* mode.height_a/b */
+			out_height_y);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			3+4*context, /* mode.width_a/b */
+			out_width_x);
+	}
+
+	for (context = MT9D131_CONTEXT_B;
+	     context <= MT9D131_CONTEXT_B;
+	     context++) {
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			39+14*context, /* mode.crop_x0_a/b */
+			crop_x0);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			41+14*context, /* mode.crop_x1_a/b */
+			crop_x0+crop_width_x);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			43+14*context, /* mode.crop_y0_a/b */
+			crop_y0);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			45+14*context, /* mode.crop_y1_a/b */
+			crop_y0+crop_height_y);
+	}
+
+	return 0;
+}
+
+/*******************************************************************************
+    Internal: write to the seq.cmd variable and wait for its effect to propagate
+
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+	"cmd" - command to write
+*******************************************************************************/
+static void mt9d131_seq_cmd(struct i2c_client *client, u8 cmd)
+{
+	mt9d131_write_register(client, MT9D131_REG8_UPROC_seq, 3, cmd);
+}
+
+/*******************************************************************************
+    Internal: Set the context (A or B) of the MT9D131 sensor
+
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+	"context" - either MT9D131_CONTEXT_A or MT9D131_CONTEXT_B
+*******************************************************************************/
+static void mt9d131_set_context(struct i2c_client *client, int context)
+{
+	if (context == MT9D131_CONTEXT_A) {
+		mt9d131_write_register(client, /* seq.captureParams = 0 */
+			MT9D131_REG8_UPROC_seq, 0x20, 0);
+		mt9d131_seq_cmd(client, 1); /* seq.cmd = do_preview */
+	} else {
+		mt9d131_write_register(client, /* seq.captureParams = 2 */
+			MT9D131_REG8_UPROC_seq, 0x20, 2);
+		mt9d131_seq_cmd(client, 2); /* seq.cmd = do_capture */
+	}
+
+	mdelay(750);
+}
+
+
+
+/*******************************************************************************
+    Set the size and position of the sensor FOV.
+    The FOV must be within the native sensor resolution, and its size must be
+    smaller than or equal to the crop window.
+
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+	"row_start"
+	"col_start" - The upper left corner of the FOV
+	"width_x"
+	"height_y" - The size of the FOV
+*******************************************************************************/
+static int mt9d131_set_sensor_fov(struct i2c_client *client,
+				u32 row_start, u32 col_start,
+				u32 width_x, u32 height_y)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	int context;
+
+	/*
+	 * sensor height/width must be a multiple of 2.  Otherwise, the
+	 * colours will be shifted.
+	 */
+	row_start &= ~1;
+	col_start &= ~1;
+	width_x  &= ~1;
+	height_y &= ~1;
+	if (((width_x + row_start) > MT9D131_MAXSENSOR_WIDTH) ||
+		((height_y + col_start) > MT9D131_MAXSENSOR_HEIGHT)) {
+		return -EINVAL;
+	}
+
+	if ((width_x  < dev->current_settings.crop_w) ||
+	    (height_y < dev->current_settings.crop_h)) {
+		return -EINVAL;
+	}
+
+	/* save the FOV size and position into the current settings. */
+	dev->current_settings.sensor_w  = width_x;
+	dev->current_settings.sensor_h  = height_y;
+	dev->current_settings.sensor_x0 = row_start;
+	dev->current_settings.sensor_y0 = col_start;
+
+	/* change to preview mode (context A) */
+	mt9d131_set_context(client, MT9D131_CONTEXT_A);
+
+	for (context = MT9D131_CONTEXT_B;
+	     context <= MT9D131_CONTEXT_B;
+	     context++) {
+		/*
+		 * (28,60) is added to row/col start to account for
+		 * the black region in the border
+		 */
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			15+12*context, row_start+28);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			17+12*context, col_start+60);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			19+12*context, height_y);
+		mt9d131_write_register(client, MT9D131_REG16_UPROC_mode,
+			21+12*context, width_x);
+	}
+
+	/* switch back to context B */
+	mt9d131_set_context(client, MT9D131_CONTEXT_B);
+
+	return 0;
+}
+
+
+/*******************************************************************************
+    Set the size and position of the sensor FOV, crop window, and output
+    window, all at the same time.  This is normally used at init time
+
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+	"sensor_x0"
+	"sensor_y0" - The upper left corner of the FOV
+	"sensor_width_x"
+	"sensor_height_y" - The size of the FOV
+	"crop_x0"
+	"crop_y0" - The upper left corner of the crop window
+	"crop_width_x"
+	"crop_height_y" - The size of the FOVcrop window
+	"out_width_x"
+	"out_height_y" - The size of the FOVcrop window
+*******************************************************************************/
+static int mt9d131_set_fov_and_window(struct i2c_client *client,
+		u32 sensor_x0, u32 sensor_y0,
+		u32 sensor_width_x, u32 sensor_height_y,
+		u32 crop_x0, u32 crop_y0,
+		u32 crop_width_x, u32 crop_height_y,
+		u32 out_width_x, u32 out_height_y)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	u32 stat;
+
+	/* First thing to check is the validity of the crop window size. */
+	if ((crop_width_x < out_width_x) ||
+	    (crop_width_x > sensor_width_x)) {
+		return -EINVAL;
+	}
+
+	if ((crop_height_y < out_height_y) ||
+	    (crop_height_y > sensor_height_y)) {
+		return -EINVAL;
+	}
+
+	/*
+	 * save the sensor geometry (that we are about to change to)
+	 * This prevents mt9d131_set_window from failing the crop
+	 * size check
+	 */
+	dev->current_settings.sensor_w  = sensor_width_x;
+	dev->current_settings.sensor_h  = sensor_height_y;
+
+	/*
+	 * since we've already checked the sanity of the crop window size,
+	 * this should never fail
+	 */
+	stat = mt9d131_set_window(client,
+			crop_x0, crop_y0,
+			crop_width_x, crop_height_y,
+			out_width_x, out_height_y);
+
+	if (stat) {
+		printk(KERN_ERR "error - invalid crop window size\n");
+		return -EINVAL;
+	}
+
+	/* Now, set the sensor FOV.  This should also never fail. */
+	return mt9d131_set_sensor_fov(client,
+			sensor_x0, sensor_y0,
+			sensor_width_x, sensor_height_y);
+}
+
+/*******************************************************************************
+    Turn on auto-exposure
+
+    Parameters:
+	"dev" - The device handle, returned by sx_mt9d131_get_device()
+*******************************************************************************/
+static void mt9d131_auto_exposure_on(struct i2c_client *client)
+{
+	u16 mode;
+
+	mt9d131_read_register(client, MT9D131_REG8_UPROC_seq, 2, &mode);
+	mode |= 0x01; /* set bit 0, which enables the AE driver */
+	mt9d131_write_register(client, MT9D131_REG8_UPROC_seq, 2, mode);
+}
+
+static int mt9d131_set_colorspace(struct i2c_client *client,
+				  enum v4l2_colorspace s)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	u16 val;
+
+	switch (s) {
+	case V4L2_COLORSPACE_470_SYSTEM_BG:
+		val = 0xf;
+		break;
+	case V4L2_COLORSPACE_REC709:
+		val = 0xd;
+		break;
+	case V4L2_COLORSPACE_JPEG:
+		val = 0x6;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mt9d131_write_register(client, MT9D131_REG_IFP1, MT9D131_IFP1_YUVCTRL,
+			       val);
+	dev->current_settings.colorspace = s;
+	return 0;
+}
+
+/*******************************************************************************
+    Internal: common init
+*******************************************************************************/
+static void mt9d131_common_init(struct i2c_client *client)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	/* mode.mode_config = 0x30  (disable JPEG on both A and B) */
+	mt9d131_write_register(client, MT9D131_REG16_UPROC_mode, 11, 0x30);
+
+	/* mode.fifo_config1_b = 0x501 (set N1 to 1, PCLK1_slew=7) */
+	mt9d131_write_register(client, MT9D131_REG16_UPROC_mode, 116, 0x5E1);
+
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_HBLANKB, MT9D131_HBLANK_MIN);
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_VBLANKB, MT9D131_VBLANK_MIN);
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_HBLANKA, MT9D131_HBLANK_MIN);
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_VBLANKA, MT9D131_VBLANK_MIN);
+
+	mt9d131_set_context(client, MT9D131_CONTEXT_B);
+
+	mt9d131_write_register(client,
+		MT9D131_REG8_UPROC_ae, 0x17, /* ae.IndexTH23 */
+		4);
+
+	mt9d131_write_register(client,
+		MT9D131_REG8_UPROC_ae, 0x0E, /* ae.maxIndex */
+		4);
+	mt9d131_write_register(client, MT9D131_REG_SENSOR,
+		MT9D131_CORE_SHUTTERWIDTH, dev->current_settings.output_h - 1);
+
+	mt9d131_seq_cmd(client, 5); /* seq.cmd = refresh */
+
+	mdelay(500);
+	mt9d131_auto_exposure_on(client);
+}
+
+static struct {
+	const char *name;
+	unsigned width;
+	unsigned height;
+} standards[] = {
+	{ "fixed 29.97fps", 1192,  892 },
+	{ "fixed 25fps",    1322,  984 },
+	{ "fixed min fps", MT9D131_MAXSENSOR_WIDTH, MT9D131_MAXSENSOR_HEIGHT },
+	{ "as fast as possible", 0,  0 }
+};
+
+static void mt9d131_reconfigure(struct i2c_client *client)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+
+	mt9d131_set_fov_and_window(client, cur->sensor_x0, cur->sensor_y0,
+				   cur->sensor_w, cur->sensor_h,
+				   cur->crop_x0, cur->crop_y0,
+				   cur->crop_w, cur->crop_h,
+				   cur->output_w, cur->output_h);
+	mt9d131_seq_cmd(client, 5); /* seq.cmd = refresh */
+}
+
+static int mt9d131_enum_input(void *ctx, struct v4l2_input *inp)
+{
+	if (inp->index)
+		return -EINVAL;
+	strcpy(inp->name, "Micron MT9D131");
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+	inp->std = (1 << ARRAY_SIZE(standards)) - 1;
+	inp->std <<= 32;
+	return 0;
+}
+
+static int mt9d131_s_input(void *ctx, unsigned i, int busy)
+{
+	return i ? -EINVAL : 0;
+}
+
+static int mt9d131_enum_std(void *ctx, struct v4l2_standard *std)
+{
+	if (std->index >= ARRAY_SIZE(standards))
+		return -EINVAL;
+	std->id = 1 << std->index;
+	std->id <<= 32; /* upper 32 bits are custom standards */
+	strcpy(std->name, standards[std->index].name);
+	std->framelines = MT9D131_MAXSENSOR_HEIGHT;
+	if (standards[std->index].width) {
+		std->frameperiod.numerator = (standards[std->index].width
+					       + MT9D131_HBLANK_MIN) *
+					     (standards[std->index].height
+					       + MT9D131_VBLANK_MIN);
+		std->frameperiod.denominator = 40000000; /* at 80MHz */
+	}
+	return 0;
+}
+
+static int mt9d131_s_std(void *ctx, v4l2_std_id *mask, int busy)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+	int i;
+
+	if (busy)
+		return -EBUSY;
+
+	for (i = 0; i < ARRAY_SIZE(standards); i++)
+		if (*mask & ((v4l2_std_id)1 << (32 + i)))
+			break;
+
+	if (i == ARRAY_SIZE(standards))
+		return -EINVAL;
+
+	*mask = (v4l2_std_id)1 << (32 + i);
+	cur->std_num = i;
+	cur->sensor_x0 += cur->crop_x0;
+	cur->sensor_y0 += cur->crop_y0;
+	if (standards[i].width) {
+		cur->sensor_w = standards[i].width;
+		cur->sensor_h = standards[i].height;
+		if (cur->crop_w > cur->sensor_w)
+			cur->crop_w = cur->sensor_w;
+		if (cur->crop_h > cur->sensor_h)
+			cur->crop_h = cur->sensor_h;
+		if (cur->output_w > cur->crop_w)
+			cur->output_w = cur->crop_w;
+		if (cur->output_h > cur->crop_h)
+			cur->output_h = cur->crop_h;
+		if (cur->sensor_x0 > MT9D131_MAXSENSOR_WIDTH - cur->sensor_w) {
+			cur->crop_x0 = cur->sensor_x0;
+			cur->sensor_x0 = MT9D131_MAXSENSOR_WIDTH
+					  - cur->sensor_w;
+			cur->crop_x0 -= cur->sensor_x0;
+		}
+		if (cur->sensor_y0 > MT9D131_MAXSENSOR_HEIGHT - cur->sensor_h) {
+			cur->crop_y0 = cur->sensor_y0;
+			cur->sensor_y0 = MT9D131_MAXSENSOR_HEIGHT
+					  - cur->sensor_h;
+			cur->crop_y0 -= cur->sensor_y0;
+		}
+	} else {
+		cur->sensor_w = cur->crop_w;
+		cur->sensor_h = cur->crop_h;
+	}
+	mt9d131_reconfigure(client);
+	return 0;
+}
+
+static int mt9d131_cropcap(void *ctx, struct v4l2_cropcap *cap)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+
+	cap->bounds.left = 0;
+	cap->bounds.top = 0;
+	cap->bounds.width = MT9D131_MAXSENSOR_WIDTH;
+	cap->bounds.height = MT9D131_MAXSENSOR_HEIGHT;
+	cap->pixelaspect.numerator = 1;
+	cap->pixelaspect.denominator = 1;
+
+	if (standards[cur->std_num].width) {
+		cap->defrect.left = ((MT9D131_MAXSENSOR_WIDTH
+				       - standards[cur->std_num].width) / 2)
+				     & ~1;
+		cap->defrect.top = ((MT9D131_MAXSENSOR_HEIGHT
+				       - standards[cur->std_num].height) / 2)
+				    & ~1;
+		cap->defrect.width = standards[cur->std_num].width;
+		cap->defrect.height = standards[cur->std_num].height;
+	} else {
+		cap->defrect = cap->bounds;
+	}
+	return 0;
+}
+
+static int mt9d131_s_crop(void *ctx, struct v4l2_crop *crop, int busy)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+
+	if (busy &&
+	    (crop->c.width != cur->crop_w || crop->c.height != cur->crop_h))
+		return -EBUSY;
+	cur->crop_x0 = (crop->c.left + (crop->c.width & 1)) & ~1;
+	cur->crop_y0 = (crop->c.top + (crop->c.height & 1)) & ~1;
+	cur->crop_w = crop->c.width & ~1;
+	cur->crop_h = crop->c.height & ~1;
+	if (!standards[cur->std_num].width) {
+		cur->sensor_w = cur->crop_w;
+		cur->sensor_h = cur->crop_h;
+		if (cur->sensor_w > MT9D131_MAXSENSOR_WIDTH)
+			cur->sensor_w = MT9D131_MAXSENSOR_WIDTH;
+		if (cur->sensor_h > MT9D131_MAXSENSOR_HEIGHT)
+			cur->sensor_h = MT9D131_MAXSENSOR_HEIGHT;
+	}
+	if (cur->crop_w > cur->sensor_w)
+		cur->crop_w = cur->sensor_w;
+	if (cur->crop_h > cur->sensor_h)
+		cur->crop_h = cur->sensor_h;
+	if (cur->output_w > cur->crop_w)
+		cur->output_w = cur->crop_w;
+	if (cur->output_h > cur->crop_h)
+		cur->output_h = cur->crop_h;
+	if (cur->crop_x0 > MT9D131_MAXSENSOR_WIDTH - cur->crop_w)
+		cur->crop_x0 = MT9D131_MAXSENSOR_WIDTH - cur->crop_w;
+	if (cur->crop_y0 > MT9D131_MAXSENSOR_HEIGHT - cur->crop_h)
+		cur->crop_y0 = MT9D131_MAXSENSOR_HEIGHT - cur->crop_h;
+	cur->sensor_x0 = cur->crop_x0;
+	if (cur->sensor_x0 > MT9D131_MAXSENSOR_WIDTH - cur->sensor_w)
+		cur->sensor_x0 = MT9D131_MAXSENSOR_WIDTH
+				  - cur->sensor_w;
+	cur->crop_x0 -= cur->sensor_x0;
+	cur->sensor_y0 = cur->crop_y0;
+	if (cur->sensor_y0 > MT9D131_MAXSENSOR_HEIGHT - cur->sensor_h)
+		cur->sensor_y0 = MT9D131_MAXSENSOR_HEIGHT
+				  - cur->sensor_h;
+	cur->crop_y0 -= cur->sensor_y0;
+	mt9d131_reconfigure(client);
+	return 0;
+}
+
+static int mt9d131_g_crop(void *ctx, struct v4l2_crop *crop)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+
+	crop->c.left = cur->sensor_x0 + cur->crop_x0;
+	crop->c.top = cur->sensor_y0 + cur->crop_y0;
+	crop->c.width = cur->crop_w;
+	crop->c.height = cur->crop_h;
+	return 0;
+}
+
+static int mt9d131_s_fmt(void *ctx, int try_fmt, struct v4l2_pix_format *fmt,
+			 int busy)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+	int maxwidth, maxheight;
+
+	if (!try_fmt && busy)
+		return -EBUSY;
+
+	fmt->pixelformat = V4L2_PIX_FMT_UYVY;
+	fmt->field = V4L2_FIELD_NONE;
+	fmt->colorspace = cur->colorspace; /* not selectable here */
+	if (standards[cur->std_num].width) {
+		maxwidth = cur->sensor_w;
+		maxheight = cur->sensor_h;
+	} else {
+		maxwidth = MT9D131_MAXSENSOR_WIDTH;
+		maxheight = MT9D131_MAXSENSOR_HEIGHT;
+	}
+	fmt->width &= ~1;
+	fmt->height &= ~1;
+	if (fmt->width > maxwidth)
+		fmt->width = maxwidth;
+	if (fmt->height > maxheight)
+		fmt->height = maxheight;
+	if (!try_fmt) {
+		cur->output_w = fmt->width;
+		cur->output_h = fmt->height;
+		if (cur->crop_w < cur->output_w)
+			cur->crop_w = cur->output_w;
+		if (cur->crop_h < cur->output_h)
+			cur->crop_h = cur->output_h;
+		cur->crop_x0 += cur->sensor_x0;
+		cur->crop_y0 += cur->sensor_y0;
+		if (cur->crop_x0 > MT9D131_MAXSENSOR_WIDTH - cur->crop_w)
+			cur->crop_x0 = MT9D131_MAXSENSOR_WIDTH - cur->crop_w;
+		if (cur->crop_y0 > MT9D131_MAXSENSOR_HEIGHT - cur->crop_h)
+			cur->crop_y0 = MT9D131_MAXSENSOR_HEIGHT - cur->crop_h;
+		if (!standards[cur->std_num].width) {
+			cur->sensor_w = cur->crop_w;
+			cur->sensor_h = cur->crop_h;
+		}
+		cur->sensor_x0 = cur->crop_x0;
+		cur->sensor_y0 = cur->crop_y0;
+		if (cur->sensor_x0 > MT9D131_MAXSENSOR_WIDTH - cur->sensor_w)
+			cur->sensor_x0 = MT9D131_MAXSENSOR_WIDTH
+					  - cur->sensor_w;
+		if (cur->sensor_y0 > MT9D131_MAXSENSOR_HEIGHT - cur->sensor_h)
+			cur->sensor_y0 = MT9D131_MAXSENSOR_HEIGHT
+					  - cur->sensor_h;
+		cur->crop_x0 -= cur->sensor_x0;
+		cur->crop_y0 -= cur->sensor_y0;
+		mt9d131_reconfigure(client);
+	}
+	return 0;
+}
+
+static int mt9d131_g_fmt(void *ctx, struct v4l2_pix_format *fmt)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+
+	fmt->pixelformat = V4L2_PIX_FMT_UYVY;
+	fmt->field = V4L2_FIELD_NONE;
+	fmt->colorspace = cur->colorspace;
+	fmt->width = cur->output_w;
+	fmt->height = cur->output_h;
+	return 0;
+}
+
+static void mt9d131_g_mode(void *ctx, struct s6dp_mode *mode)
+{
+	struct i2c_client *client = ctx;
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	struct mt9d131_settings *cur = &dev->current_settings;
+
+	mode->type = S6_DP_VIDEO_CFG_MODE_422_SERIAL;
+	mode->progressive = 1;
+	mode->embedded_sync = 0;
+	mode->micron_mode = 1;
+	mode->vsync_pol = 0;
+	mode->hsync_pol = 0;
+	mode->blank_pol = 0;
+	mode->field_ctrl = 0;
+	mode->blank_ctrl = 0;
+	mode->ten_bit = 0;
+	mode->line_and_crc = 0;
+	mode->pixel_total = cur->output_w + 6;
+	mode->pixel_offset = 0;
+	mode->pixel_active = cur->output_w;
+	mode->pixel_padding = 0;
+	mode->framelines = cur->output_h + 1;
+	mode->odd_vsync_offset = cur->output_h;
+	mode->odd_vsync_len = 1;
+	mode->odd_first = 0;
+	mode->odd_active = cur->output_h;
+	mode->odd_total = cur->output_h + 1;
+	mode->even_vsync_offset = 0;
+	mode->even_vsync_len = 0;
+	mode->even_first = 0;
+	mode->even_active = 0;
+	mode->hsync_offset = 0;
+	mode->hsync_len = 1;
+}
+
+static int mt9d131_probe(struct i2c_client *new_client,
+			 const struct i2c_device_id *id)
+{
+	struct s6dp_link *link;
+	struct mt9d131_device *dev;
+	struct mt9d131_settings *cur;
+	int ret;
+
+	if (!i2c_check_functionality(new_client->adapter,
+				     I2C_FUNC_SMBUS_WORD_DATA))
+		return -ENODEV;
+	link = new_client->dev.platform_data;
+	if (!link)
+		return -EINVAL;
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+	dev->current_reg_set = -1;
+	i2c_set_clientdata(new_client, dev);
+
+	ret = mt9d131_setup_pll(new_client, 12, 80);
+	if (ret < 0) {
+		kfree(dev);
+		return ret;
+	}
+
+	link->g_mode = mt9d131_g_mode;
+	link->e_std = mt9d131_enum_std;
+	link->s_std = mt9d131_s_std;
+	link->s_fmt = mt9d131_s_fmt;
+	link->g_fmt = mt9d131_g_fmt;
+	link->cropcap = mt9d131_cropcap;
+	link->s_crop = mt9d131_s_crop;
+	link->g_crop = mt9d131_g_crop;
+	link->dir.ingress.e_inp = mt9d131_enum_input;
+	link->dir.ingress.s_inp = mt9d131_s_input;
+	link->context = new_client;
+
+	mt9d131_write_register(new_client, MT9D131_REG_IFP1,
+			       MT9D131_IFP1_YRGBOFFSET, 16 << 8);
+	mt9d131_set_colorspace(new_client, V4L2_COLORSPACE_470_SYSTEM_BG);
+	cur = &dev->current_settings;
+	cur->std_num = ARRAY_SIZE(standards) - 1;
+	cur->sensor_w = MT9D131_MAXSENSOR_WIDTH;
+	cur->sensor_h = MT9D131_MAXSENSOR_HEIGHT;
+	cur->crop_w = cur->sensor_w;
+	cur->crop_h = cur->sensor_h;
+	cur->output_w = 48;
+	cur->output_h = 32;
+	cur->sensor_x0 = 0;
+	cur->sensor_y0 = 0;
+	cur->crop_x0 = 0;
+	cur->crop_y0 = 0;
+	mt9d131_reconfigure(new_client);
+	mt9d131_common_init(new_client);
+	printk(KERN_INFO "successfully probed mt9d131\n");
+	return 0;
+}
+
+static int mt9d131_remove(struct i2c_client *client)
+{
+	struct mt9d131_device *dev = i2c_get_clientdata(client);
+	i2c_set_clientdata(client, 0);
+	kfree(dev);
+	return 0;
+}
+
+static const struct i2c_device_id mt9d131_id[] = {
+	{ "mt9d131", 0 },
+	{ }
+};
+
+static struct i2c_driver mt9d131_driver = {
+	.driver = {
+		.name	= "s6dp-mt9d131",
+	},
+	.probe  = mt9d131_probe,
+	.remove = mt9d131_remove,
+	.id_table = mt9d131_id,
+};
+
+static int __init mt9d131_init(void)
+{
+	return i2c_add_driver(&mt9d131_driver);
+}
+
+static void __exit mt9d131_exit(void)
+{
+	i2c_del_driver(&mt9d131_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Fabian Godehardt");
+
+module_init(mt9d131_init);
+module_exit(mt9d131_exit);
-- 
1.6.2.107.ge47ee


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

* [patch 5/5] saa7121 driver for s6000 data port
  2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
                   ` (2 preceding siblings ...)
  2009-03-26 14:36 ` [patch 4/5] mt9d131 driver for s6000 data port Daniel Glöckner
@ 2009-03-26 14:36 ` Daniel Glöckner
  3 siblings, 0 replies; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Chris Zankel, linux-media, Daniel Glöckner

This patch adds a driver to support the saa7121 PAL/NTSC video encoder
in combination with the s6000 data port driver.

The chip is configured for embedded BT.656 syncs as this mode should
be supported on all devices.

The driver presents two outputs to applications and while it is true
that the device has these two outputs, both of them are always active.
The only difference on the "Y/C" output is that it disables the luma
notch filter.

Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
 drivers/media/video/s6dp/Kconfig        |    7 +
 drivers/media/video/s6dp/Makefile       |    1 +
 drivers/media/video/s6dp/s6dp-saa7121.c |  478 +++++++++++++++++++++++++++++++
 3 files changed, 486 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/video/s6dp/s6dp-saa7121.c

diff --git a/drivers/media/video/s6dp/Kconfig b/drivers/media/video/s6dp/Kconfig
index 853e6b1..c95904c 100644
--- a/drivers/media/video/s6dp/Kconfig
+++ b/drivers/media/video/s6dp/Kconfig
@@ -20,3 +20,10 @@ config VIDEO_S6DP_MT9D131
 	default n
 	help
 	  Enables the MT9D131 camera driver.
+
+config VIDEO_S6DP_SAA7121
+	tristate "SAA7121 video encoder"
+	depends on VIDEO_S6000
+	default n
+	help
+	  Enables the SAA7121 video encoder driver.
diff --git a/drivers/media/video/s6dp/Makefile b/drivers/media/video/s6dp/Makefile
index af0bc0f..61d86c9 100644
--- a/drivers/media/video/s6dp/Makefile
+++ b/drivers/media/video/s6dp/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_VIDEO_S6000) += s6dp.o
 obj-$(CONFIG_VIDEO_S6DP_MT9D131) += s6dp-mt9d131.o
+obj-$(CONFIG_VIDEO_S6DP_SAA7121) += s6dp-saa7121.o
diff --git a/drivers/media/video/s6dp/s6dp-saa7121.c b/drivers/media/video/s6dp/s6dp-saa7121.c
new file mode 100644
index 0000000..70799cd
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp-saa7121.c
@@ -0,0 +1,478 @@
+/*
+ * drivers/media/video/s6dp/s6dp-saa7121.c
+ *
+ * Description: Driver for SAA7121 chips hooked up to a S6000 family data port
+ *	(c) 2009 emlix GmbH <info@emlix.com>
+ *
+ * Author:	Daniel Gloeckner <dg@emlix.com>
+ */
+
+#include <media/s6dp-link.h>
+#include "s6dp.h"
+#include <linux/i2c.h>
+
+static const u8 initial_setup[][2] = {
+	{0x3a, 0x13}
+};
+
+static const u8 pal_values[][2] = {
+	{0x28, 33}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 125},
+	{0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0x40+53},
+	{0x61, 0x06}, {0x62, 47}, {0x63, 0xcb}, {0x64, 0x8a},
+	{0x65, 0x09}, {0x66, 0x2a}, {0x6c, 0x05}, {0x6d, 0x20},
+	{0x6e, 0xa0}
+};
+
+static const u8 pal_nc_values[][2] = {
+	{0x28, 33}, {0x29, 37}, {0x5a, 0x00}, {0x5b, 125},
+	{0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0xc0+53},
+	{0x61, 0x06}, {0x62, 47}, {0x63, 0x46}, {0x64, 0x94},
+	{0x65, 0xf6}, {0x66, 0x21}, {0x6c, 0x05}, {0x6d, 0x20},
+	{0x6e, 0xa0}
+};
+
+static const u8 pal_m_values[][2] = {
+	{0x28, 25}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 118},
+	{0x5c, 165}, {0x5d, 45}, {0x5e, 49}, {0x5f, 0xc0+59},
+	{0x61, 0x17}, {0x62, 45}, {0x63, 0xe3}, {0x64, 0xef},
+	{0x65, 0xe6}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
+	{0x6e, 0xa0}
+};
+
+static const u8 ntsc_values[][2] = {
+	{0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
+	{0x5c, 165}, {0x5d, 42}, {0x5e, 46}, {0x5f, 0xc0+46},
+	{0x61, 0x15}, {0x62, 63}, {0x63, 0x1f}, {0x64, 0x7c},
+	{0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
+	{0x6e, 0x80}
+};
+
+static const u8 ntsc_jp_values[][2] = {
+	{0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
+	{0x5c, 165}, {0x5d, 19}, {0x5e, 46}, {0x5f, 0xc0+46},
+	{0x61, 0x05}, {0x62, 62}, {0x63, 0x1f}, {0x64, 0x7c},
+	{0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
+	{0x6e, 0x80}
+};
+
+struct saa7121 {
+	int std;
+	int yc;
+	struct v4l2_pix_format fmt;
+	struct v4l2_rect crop;
+	u8 regs[128];
+};
+
+static int saa7121_write_regs(struct i2c_client *client)
+{
+	struct saa7121 *me = i2c_get_clientdata(client);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(me->regs); i++) {
+		int ret = i2c_smbus_write_byte_data(client, i, me->regs[i]);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static void saa7121_change_regs(struct saa7121 *me, const u8 (*regs)[2],
+				int num)
+{
+	int i;
+	for (i = 0; i < num; i++)
+		me->regs[regs[i][0]] = regs[i][1];
+}
+
+static const struct {
+	v4l2_std_id mask;
+	const char *name;
+	const u8 (*regs)[2];
+	int num;
+} standards[] = {
+	{
+		V4L2_STD_PAL | V4L2_STD_PAL_N,
+		"PAL",
+		pal_values,
+		ARRAY_SIZE(pal_values)
+	},
+	{
+		V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
+		"NTSC",
+		ntsc_values,
+		ARRAY_SIZE(ntsc_values)
+	},
+	{
+		V4L2_STD_NTSC_M_JP,
+		"NTSC-JP",
+		ntsc_jp_values,
+		ARRAY_SIZE(ntsc_jp_values)
+	},
+	{
+		V4L2_STD_PAL_M,
+		"PAL-M",
+		pal_m_values,
+		ARRAY_SIZE(pal_nc_values)
+	},
+	{
+		V4L2_STD_PAL_Nc,
+		"PAL-Nc",
+		pal_nc_values,
+		ARRAY_SIZE(pal_nc_values)
+	},
+};
+
+static int saa7121_e_std(void *ctx, struct v4l2_standard *std)
+{
+	if (std->index >= ARRAY_SIZE(standards))
+		return -EINVAL;
+	std->id = standards[std->index].mask;
+	strcpy(std->name, standards[std->index].name);
+	if (std->id & V4L2_STD_625_50) {
+		std->frameperiod.numerator = 1;
+		std->frameperiod.denominator = 25;
+		std->framelines = 625;
+	} else  {
+		std->frameperiod.numerator = 1001;
+		std->frameperiod.denominator = 30000;
+		std->framelines = 525;
+	}
+	return 0;
+}
+
+static int saa7121_cropcap(void *ctx, struct v4l2_cropcap *cap)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+
+	if (standards[me->std].mask & V4L2_STD_625_50) {
+		cap->bounds.top = 23 * 2;
+		cap->bounds.left = 132;
+		cap->bounds.width = 720;
+		cap->bounds.height = 576;
+		cap->defrect.left = 140;
+		cap->defrect.top = 23 * 2;
+		cap->defrect.width = 702;
+		cap->defrect.height = 576;
+		cap->pixelaspect.numerator = 54;
+		cap->pixelaspect.denominator = 59;
+	} else {
+		cap->bounds.top = 20 * 2;
+		cap->bounds.left = 122;
+		cap->bounds.width = 720;
+		cap->bounds.height = 487;
+		cap->defrect.left = 130;
+		cap->defrect.top = 22 * 2;
+		cap->defrect.width = 704;
+		cap->defrect.height = 480;
+		cap->pixelaspect.numerator = 11;
+		cap->pixelaspect.denominator = 10;
+	}
+	return 0;
+}
+
+static int saa7121_s_crop(void *ctx, struct v4l2_crop *crop, int busy)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+	struct v4l2_cropcap cap;
+
+	if (busy)
+		return -EBUSY;
+
+	saa7121_cropcap(ctx, &cap);
+	me->crop = crop->c;
+
+	if (me->crop.width > cap.bounds.width)
+		me->crop.width = cap.bounds.width;
+	me->crop.left += me->crop.width & 1;
+	me->crop.width &= ~1;
+	if (me->crop.height > cap.bounds.height)
+		me->crop.height = cap.bounds.height;
+	if (me->crop.left < cap.bounds.left)
+		me->crop.left = cap.bounds.left;
+	if (me->crop.left > cap.bounds.left + cap.bounds.width - me->crop.width)
+		me->crop.left = cap.bounds.left + cap.bounds.width
+				 - me->crop.width;
+	me->crop.left &= ~1;
+	if (me->crop.top < cap.bounds.top)
+		me->crop.top = cap.bounds.top;
+	if (me->crop.top > cap.bounds.top + cap.bounds.height - me->crop.height)
+		me->crop.top = cap.bounds.top + cap.bounds.height
+				- me->crop.height;
+	me->crop.top &= ~1;
+	me->fmt.width = me->crop.width;
+	me->fmt.height = me->crop.height;
+	return 0;
+}
+
+static int saa7121_g_crop(void *ctx, struct v4l2_crop *crop)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+
+	crop->c = me->crop;
+	return 0;
+}
+
+static int saa7121_s_fmt(void *ctx, int try_fmt, struct v4l2_pix_format *fmt,
+			 int busy)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+	struct v4l2_cropcap cap;
+
+	if (!try_fmt && busy)
+		return -EBUSY;
+
+	saa7121_cropcap(ctx, &cap);
+	fmt->pixelformat = V4L2_PIX_FMT_UYVY;
+	fmt->field = V4L2_FIELD_ALTERNATE;
+
+	if (standards[me->std].mask & V4L2_STD_625_50)
+		fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
+	else if (standards[me->std].mask == V4L2_STD_PAL_M)
+		fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
+	else
+		fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	fmt->width &= ~1;
+	if (fmt->width > cap.bounds.width)
+		fmt->width = cap.bounds.width;
+	if (fmt->height > cap.bounds.height)
+		fmt->height = cap.bounds.height;
+
+	if (!try_fmt) {
+		me->fmt = *fmt;
+		me->crop.width = fmt->width;
+		me->crop.height = fmt->height;
+		if (me->crop.left > cap.bounds.left + cap.bounds.width
+				     - fmt->width) {
+			me->crop.left = cap.bounds.left + cap.bounds.width
+					 - fmt->width;
+			me->crop.left &= ~1;
+		}
+		if (me->crop.top > cap.bounds.top + cap.bounds.height
+				    - fmt->height) {
+			me->crop.top = cap.bounds.top + cap.bounds.height
+					- fmt->height;
+			me->crop.top &= ~1;
+		}
+	}
+	return 0;
+}
+
+static int saa7121_g_fmt(void *ctx, struct v4l2_pix_format *fmt)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+	*fmt = me->fmt;
+	return 0;
+}
+
+static void saa7121_g_mode(void *ctx, struct s6dp_mode *mode)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+	struct v4l2_cropcap cap;
+
+	saa7121_cropcap(ctx, &cap);
+	mode->type = S6_DP_VIDEO_CFG_MODE_422_SERIAL;
+	mode->progressive = 0;
+	mode->embedded_sync = 1;
+	mode->relaxed_framing = 0;
+	mode->ten_bit = 0;
+	mode->line_and_crc = 0;
+	mode->micron_mode = 0;
+	if (standards[me->std].mask & V4L2_STD_625_50) {
+		mode->pixel_total = 864;
+		mode->framelines = 625;
+		mode->odd_vsync_offset = 623;
+		mode->odd_vsync_len = 24;
+		mode->odd_first = 22;
+		mode->odd_total = 312;
+		mode->even_vsync_offset = 310;
+		mode->even_vsync_len = 25;
+		mode->even_first = 335;
+		mode->hsync_offset = 0;
+		mode->hsync_len = 4;
+	} else {
+		mode->pixel_total = 858;
+		mode->framelines = 525;
+		mode->odd_vsync_offset = 522;
+		mode->odd_vsync_len = 19;
+		mode->odd_first = 16;
+		mode->odd_total = 262;
+		mode->even_vsync_offset = 260;
+		mode->even_vsync_len = 19;
+		mode->even_first = 279;
+		mode->hsync_offset = 0;
+		mode->hsync_len = 4;
+	}
+	mode->even_active = me->crop.height / 2;
+	mode->odd_active = me->crop.height - mode->even_active;
+	mode->odd_first += (me->crop.top - cap.bounds.top) / 2;
+	mode->even_first += (me->crop.top - cap.bounds.top) / 2;
+	mode->pixel_active = me->crop.width;
+	mode->pixel_offset = me->crop.left - cap.bounds.left;
+	mode->pixel_padding = 720 - mode->pixel_active - mode->pixel_offset;
+}
+
+static void saa7121_reconfigure(struct i2c_client *client)
+{
+	struct saa7121 *me = i2c_get_clientdata(client);
+
+	saa7121_change_regs(me, standards[me->std].regs,
+			    standards[me->std].num);
+	if (me->yc)
+		me->regs[0x5f] &= 0x3f;
+
+	saa7121_write_regs(client);
+}
+
+static int saa7121_s_std(void *ctx, v4l2_std_id *mask, int busy)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+	int i;
+
+	if (busy && !(standards[me->std].mask & V4L2_STD_625_50)
+		     == !(*mask & V4L2_STD_625_50))
+		return -EBUSY;
+
+	for (i = 0; i < ARRAY_SIZE(standards); i++) {
+		if (standards[i].mask & *mask) {
+			me->std = i;
+			saa7121_reconfigure(client);
+			*mask = standards[i].mask;
+			saa7121_s_fmt(ctx, 0, &me->fmt, 0);
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static int saa7121_e_outp(void *ctx, struct v4l2_output *outp)
+{
+	int i;
+	if (outp->index > 1)
+		return -EINVAL;
+
+	outp->type = V4L2_OUTPUT_TYPE_ANALOG;
+	for (i = 0; i < ARRAY_SIZE(standards); i++)
+		outp->std |= standards[i].mask;
+
+	strcpy(outp->name, outp->index ? "Y/C" : "CVBS");
+	return 0;
+}
+
+static int saa7121_s_outp(void *ctx, unsigned int nr, int busy)
+{
+	struct i2c_client *client = ctx;
+	struct saa7121 *me = i2c_get_clientdata(client);
+
+	if (nr > 1)
+		return -EINVAL;
+
+	/*
+	 * both outputs are always active
+	 * we just disable the cross color filter for Y/C
+	 */
+	me->yc = nr;
+	saa7121_reconfigure(client);
+	return 0;
+}
+
+static int saa7121_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct saa7121 *me;
+	struct s6dp_link *link;
+	s32 val;
+
+	if (!client->dev.platform_data)
+		return -EINVAL;
+	link = client->dev.platform_data;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE
+					| I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
+		return -ENODEV;
+
+	val = i2c_smbus_read_byte(client);
+	if (val < 0) {
+		printk(KERN_ERR "saa7121: can't read status byte\n");
+		return -EIO;
+	}
+	if ((val & 0xE0) != 0x20) {
+		printk(KERN_ERR "saa7121: unsupported chip version %i"
+				" (status = 0x%02x)\n", val >> 5, val);
+		return -ENODEV;
+	}
+	me = kzalloc(sizeof(*me), GFP_KERNEL);
+	if (!me)
+		return -ENOMEM;
+	me->std = V4L2_STD_PAL;
+	i2c_set_clientdata(client, me);
+
+	saa7121_change_regs(me, initial_setup, ARRAY_SIZE(initial_setup));
+	saa7121_change_regs(me, pal_values, ARRAY_SIZE(pal_values));
+	if (saa7121_write_regs(client) < 0) {
+		printk(KERN_ERR "saa7121: can't write registers\n");
+		kfree(me);
+		return -EIO;
+	}
+
+	link->g_mode = saa7121_g_mode;
+	link->e_std = saa7121_e_std;
+	link->s_std = saa7121_s_std;
+	link->s_fmt = saa7121_s_fmt;
+	link->g_fmt = saa7121_g_fmt;
+	link->cropcap = saa7121_cropcap;
+	link->s_crop = saa7121_s_crop;
+	link->g_crop = saa7121_g_crop;
+	link->dir.egress.e_outp = saa7121_e_outp;
+	link->dir.egress.s_outp = saa7121_s_outp;
+	link->context = client;
+	printk(KERN_INFO "saa7121 probed successfully\n");
+	return 0;
+}
+
+static int saa7121_remove(struct i2c_client *client)
+{
+	struct saa7121_data *data;
+	data = i2c_get_clientdata(client);
+	i2c_set_clientdata(client, NULL);
+	kfree(data);
+	return 0;
+}
+
+static const struct i2c_device_id saa7121_id[] = {
+	{ "saa7121", 0 },
+	{ }
+};
+
+static struct i2c_driver saa7121_driver = {
+	.driver = {
+		.name   = "s6dp-saa7121",
+	},
+	.probe          = saa7121_probe,
+	.remove         = saa7121_remove,
+	.id_table	= saa7121_id,
+};
+
+static int __init saa7121_init(void)
+{
+	return i2c_add_driver(&saa7121_driver);
+}
+
+static void __exit saa7121_exit(void)
+{
+	i2c_del_driver(&saa7121_driver);
+}
+
+MODULE_AUTHOR("Daniel Gloeckner <dg@emlix.com>");
+MODULE_DESCRIPTION("SAA7121 driver");
+MODULE_LICENSE("GPL");
+
+module_init(saa7121_init);
+module_exit(saa7121_exit);
-- 
1.6.2.107.ge47ee


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

* Re: [patch 5/5] saa7121 driver for s6000 data port
  2009-03-30 13:36         ` Daniel Glöckner
@ 2009-03-30 13:41           ` Hans Verkuil
  0 siblings, 0 replies; 12+ messages in thread
From: Hans Verkuil @ 2009-03-30 13:41 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media

On Monday 30 March 2009 15:36:42 Daniel Glöckner wrote:
> On 03/30/2009 02:50 PM, Hans Verkuil wrote:
> > On Monday 30 March 2009 14:12:10 Daniel Glöckner wrote:
> >> - valid CRC and line number in digital blanking?
> > 
> > Do you really need to control these?
> 
> Not on this board..
> 
> > It's a PAL/NTSC encoder, so the standard specified with s_std_output will
> > map to the corresponding values that you need to put in. This is knowledge
> > that the i2c driver implements.
> 
> There is a micron camera connected to the controller that can output any
> resolution up to 1600x1200 and we don't have standard ids for all those HD
> formats supported by encoders like the ADV7197. It would be really nice to
> have an interface that covers all this while being symmetric for input and output.

This is a known issue. I expect to see some proposals for this in the near
future since Texas Instruments need this as well for their davinci architecture.

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG

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

* Re: [patch 5/5] saa7121 driver for s6000 data port
  2009-03-30 12:50       ` Hans Verkuil
@ 2009-03-30 13:36         ` Daniel Glöckner
  2009-03-30 13:41           ` Hans Verkuil
  0 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-30 13:36 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media

On 03/30/2009 02:50 PM, Hans Verkuil wrote:
> On Monday 30 March 2009 14:12:10 Daniel Glöckner wrote:
>> - valid CRC and line number in digital blanking?
> 
> Do you really need to control these?

Not on this board..

> It's a PAL/NTSC encoder, so the standard specified with s_std_output will
> map to the corresponding values that you need to put in. This is knowledge
> that the i2c driver implements.

There is a micron camera connected to the controller that can output any
resolution up to 1600x1200 and we don't have standard ids for all those HD
formats supported by encoders like the ADV7197. It would be really nice to
have an interface that covers all this while being symmetric for input and output.

  Daniel


-- 
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax -11, Bahnhofsallee 1b, 37081 Göttingen, Germany
Geschäftsführung: Dr. Uwe Kracke, Dr. Cord Seele, Ust-IdNr.: DE 205 198 055
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160

emlix - your embedded linux partner

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

* Re: [patch 5/5] saa7121 driver for s6000 data port
  2009-03-30 12:12     ` Daniel Glöckner
@ 2009-03-30 12:50       ` Hans Verkuil
  2009-03-30 13:36         ` Daniel Glöckner
  0 siblings, 1 reply; 12+ messages in thread
From: Hans Verkuil @ 2009-03-30 12:50 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media

On Monday 30 March 2009 14:12:10 Daniel Glöckner wrote:
> On 03/30/2009 12:03 PM, Hans Verkuil wrote:
> > What exactly do you need? If there is something missing, then it should be 
> > added. But my guess is that you can pass such information via the s_routing 
> > callback. That's what all other drivers that use v4l2_subdev do.
> 
> The s_routing callback looks very limited. One can pass only two u32 values.

If a driver needs it, it can be extended. In particular I always thought that
a third config value would be useful.

> The parameters that have to be negotiated are:
> - What is the on-wire video format?

That might go to such a config value.

> - how many data lines are connected?

routing

> - synchronization using embedded SAV/EAV codes or using dedicated pins?

config value and/or routing.

> - polarity of sync lines?

config

> - valid CRC and line number in digital blanking?

Do you really need to control these?

> - what is the layout of the digital image?
> - how many odd lines are there? how many even? (including blanking)
> - how many horizontal pixels? (incl. blanking)
> - where is the active region?
> - on which pixels/lines do we start/end horizontal/vertical sync?

It's a PAL/NTSC encoder, so the standard specified with s_std_output will
map to the corresponding values that you need to put in. This is knowledge
that the i2c driver implements.

> 
> >> It seems the soc-camera framework is a better choice here, but to make it
> >> work with the saa7121 one would first have to implement support for video
> >> output.
> > 
> > This framework will also be converted to use v4l2_subdev for the 
> > communication with i2c drivers.
> 
> So it shouldn't matter which one I chose?

You will have to do the work anyway. Better to go with the new framework then
having to do the work twice.

> > Actually, I recommend that you first look at the existing saa7127.c source.
> > I don't know how many differences there are between the saa7121 and 
> > saa7127, but perhaps support for the saa7121 can be added there rather than 
> > introducing a new driver. Of course, that only works if the differences are 
> > not too big.
> 
> The chips appear to be very similar, sharing most of the registers. However, the
> aforementioned problem still exists with this driver. A driver connecting this
> sub device must know beforehand that it has to send standard BT.656 video frames
> with SAV/EAV codes.

So? If some future driver wants to do this differently, then we add the
necessary code to the i2c driver. It's not fixed in stone, you know :-)

Basically a driver only implements what can be tested. There is little point
in adding a full feature set for a device if you are unable to test the code
as well. So if a newer board appears in the future that needs to use
something new, then we add support for that to the i2c driver.

Looking at the datasheets I don't think you should make a new driver for
this. Unless something crops up that makes it hard to use saa7127.c I think
you should extend that driver to support saa7121 and add support for the
missing functionality. But only what is necessary for your setup.

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG

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

* Re: [patch 5/5] saa7121 driver for s6000 data port
  2009-03-30 10:03   ` Hans Verkuil
@ 2009-03-30 12:12     ` Daniel Glöckner
  2009-03-30 12:50       ` Hans Verkuil
  0 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-30 12:12 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media

On 03/30/2009 12:03 PM, Hans Verkuil wrote:
> What exactly do you need? If there is something missing, then it should be 
> added. But my guess is that you can pass such information via the s_routing 
> callback. That's what all other drivers that use v4l2_subdev do.

The s_routing callback looks very limited. One can pass only two u32 values.

The parameters that have to be negotiated are:
- What is the on-wire video format?
- how many data lines are connected?
- synchronization using embedded SAV/EAV codes or using dedicated pins?
- polarity of sync lines?
- valid CRC and line number in digital blanking?
- what is the layout of the digital image?
- how many odd lines are there? how many even? (including blanking)
- how many horizontal pixels? (incl. blanking)
- where is the active region?
- on which pixels/lines do we start/end horizontal/vertical sync?

>> It seems the soc-camera framework is a better choice here, but to make it
>> work with the saa7121 one would first have to implement support for video
>> output.
> 
> This framework will also be converted to use v4l2_subdev for the 
> communication with i2c drivers.

So it shouldn't matter which one I chose?

> Actually, I recommend that you first look at the existing saa7127.c source.
> I don't know how many differences there are between the saa7121 and 
> saa7127, but perhaps support for the saa7121 can be added there rather than 
> introducing a new driver. Of course, that only works if the differences are 
> not too big.

The chips appear to be very similar, sharing most of the registers. However, the
aforementioned problem still exists with this driver. A driver connecting this
sub device must know beforehand that it has to send standard BT.656 video frames
with SAV/EAV codes.

  Daniel


-- 
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax -11, Bahnhofsallee 1b, 37081 Göttingen, Germany
Geschäftsführung: Dr. Uwe Kracke, Dr. Cord Seele, Ust-IdNr.: DE 205 198 055
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160

emlix - your embedded linux partner

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

* Re: [patch 5/5] saa7121 driver for s6000 data port
  2009-03-30  9:56 ` Daniel Glöckner
@ 2009-03-30 10:03   ` Hans Verkuil
  2009-03-30 12:12     ` Daniel Glöckner
  0 siblings, 1 reply; 12+ messages in thread
From: Hans Verkuil @ 2009-03-30 10:03 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media

On Monday 30 March 2009 11:56:25 Daniel Glöckner wrote:
> On 03/26/2009 04:08 PM, Hans Verkuil wrote:
> > I've been working on a new framework for devices like this and almost
> > all i2c v4l drivers are now converted to v4l2_subdev in our v4l-dvb
> > tree. It will also be merged in 2.6.30. Please take a look at
> > v4l2-framework.txt in the v4l-dvb repository for more information.
> >
> > I'm sure you will have questions later, please don't hesitate to ask!
> > It's a recent development but very much needed. Otherwise we will end
> > up with a lot of duplicate i2c drivers, each tied to their own platform
> > or framework. That's clearly something we do not want.
>
> Hi Hans,
>
> the problem I see with the v4l2-framework in this case is that in its
> current state it does not allow to exchange information regarding the bus
> parameters between the sub device and the controller.

What exactly do you need? If there is something missing, then it should be 
added. But my guess is that you can pass such information via the s_routing 
callback. That's what all other drivers that use v4l2_subdev do.

> It seems the soc-camera framework is a better choice here, but to make it
> work with the saa7121 one would first have to implement support for video
> output.

This framework will also be converted to use v4l2_subdev for the 
communication with i2c drivers.

> What do you recommend?

Actually, I recommend that you first look at the existing saa7127.c source. 
I don't know how many differences there are between the saa7121 and 
saa7127, but perhaps support for the saa7121 can be added there rather than 
introducing a new driver. Of course, that only works if the differences are 
not too big.

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG

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

* Re: [patch 5/5] saa7121 driver for s6000 data port
  2009-03-26 15:08 Hans Verkuil
@ 2009-03-30  9:56 ` Daniel Glöckner
  2009-03-30 10:03   ` Hans Verkuil
  0 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2009-03-30  9:56 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media

On 03/26/2009 04:08 PM, Hans Verkuil wrote:
> I've been working on a new framework for devices like this and almost all
> i2c v4l drivers are now converted to v4l2_subdev in our v4l-dvb tree. It
> will also be merged in 2.6.30. Please take a look at v4l2-framework.txt in
> the v4l-dvb repository for more information.
> 
> I'm sure you will have questions later, please don't hesitate to ask! It's
> a recent development but very much needed. Otherwise we will end up with a
> lot of duplicate i2c drivers, each tied to their own platform or
> framework. That's clearly something we do not want.

Hi Hans,

the problem I see with the v4l2-framework in this case is that in its current
state it does not allow to exchange information regarding the bus parameters
between the sub device and the controller.

It seems the soc-camera framework is a better choice here, but to make it work
with the saa7121 one would first have to implement support for video output.

What do you recommend?


  Daniel

-- 
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax -11, Bahnhofsallee 1b, 37081 Göttingen, Germany
Geschäftsführung: Dr. Uwe Kracke, Dr. Cord Seele, Ust-IdNr.: DE 205 198 055
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160

emlix - your embedded linux partner

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

* Re: [patch 5/5] saa7121 driver for s6000 data port
@ 2009-03-26 15:08 Hans Verkuil
  2009-03-30  9:56 ` Daniel Glöckner
  0 siblings, 1 reply; 12+ messages in thread
From: Hans Verkuil @ 2009-03-26 15:08 UTC (permalink / raw)
  To: Daniel Glöckner
  Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media,
	Daniel Glöckner


> This patch adds a driver to support the saa7121 PAL/NTSC video encoder
> in combination with the s6000 data port driver.
>
> The chip is configured for embedded BT.656 syncs as this mode should
> be supported on all devices.
>
> The driver presents two outputs to applications and while it is true
> that the device has these two outputs, both of them are always active.
> The only difference on the "Y/C" output is that it disables the luma
> notch filter.

Hi Daniel,

The big problem with this driver and the micron driver is that this
implementation ties it directly to the s6000 data port driver. Thus making
it impossible to reuse in another system.

I've been working on a new framework for devices like this and almost all
i2c v4l drivers are now converted to v4l2_subdev in our v4l-dvb tree. It
will also be merged in 2.6.30. Please take a look at v4l2-framework.txt in
the v4l-dvb repository for more information.

I'm sure you will have questions later, please don't hesitate to ask! It's
a recent development but very much needed. Otherwise we will end up with a
lot of duplicate i2c drivers, each tied to their own platform or
framework. That's clearly something we do not want.

Regards,

         Hans

>
> Signed-off-by: Daniel Glöckner <dg@emlix.com>
> ---
>  drivers/media/video/s6dp/Kconfig        |    7 +
>  drivers/media/video/s6dp/Makefile       |    1 +
>  drivers/media/video/s6dp/s6dp-saa7121.c |  478
> +++++++++++++++++++++++++++++++
>  3 files changed, 486 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/media/video/s6dp/s6dp-saa7121.c
>
> diff --git a/drivers/media/video/s6dp/Kconfig
> b/drivers/media/video/s6dp/Kconfig
> index 853e6b1..c95904c 100644
> --- a/drivers/media/video/s6dp/Kconfig
> +++ b/drivers/media/video/s6dp/Kconfig
> @@ -20,3 +20,10 @@ config VIDEO_S6DP_MT9D131
>  	default n
>  	help
>  	  Enables the MT9D131 camera driver.
> +
> +config VIDEO_S6DP_SAA7121
> +	tristate "SAA7121 video encoder"
> +	depends on VIDEO_S6000
> +	default n
> +	help
> +	  Enables the SAA7121 video encoder driver.
> diff --git a/drivers/media/video/s6dp/Makefile
> b/drivers/media/video/s6dp/Makefile
> index af0bc0f..61d86c9 100644
> --- a/drivers/media/video/s6dp/Makefile
> +++ b/drivers/media/video/s6dp/Makefile
> @@ -1,2 +1,3 @@
>  obj-$(CONFIG_VIDEO_S6000) += s6dp.o
>  obj-$(CONFIG_VIDEO_S6DP_MT9D131) += s6dp-mt9d131.o
> +obj-$(CONFIG_VIDEO_S6DP_SAA7121) += s6dp-saa7121.o
> diff --git a/drivers/media/video/s6dp/s6dp-saa7121.c
> b/drivers/media/video/s6dp/s6dp-saa7121.c
> new file mode 100644
> index 0000000..70799cd
> --- /dev/null
> +++ b/drivers/media/video/s6dp/s6dp-saa7121.c
> @@ -0,0 +1,478 @@
> +/*
> + * drivers/media/video/s6dp/s6dp-saa7121.c
> + *
> + * Description: Driver for SAA7121 chips hooked up to a S6000 family data
> port
> + *	(c) 2009 emlix GmbH <info@emlix.com>
> + *
> + * Author:	Daniel Gloeckner <dg@emlix.com>
> + */
> +
> +#include <media/s6dp-link.h>
> +#include "s6dp.h"
> +#include <linux/i2c.h>
> +
> +static const u8 initial_setup[][2] = {
> +	{0x3a, 0x13}
> +};
> +
> +static const u8 pal_values[][2] = {
> +	{0x28, 33}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 125},
> +	{0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0x40+53},
> +	{0x61, 0x06}, {0x62, 47}, {0x63, 0xcb}, {0x64, 0x8a},
> +	{0x65, 0x09}, {0x66, 0x2a}, {0x6c, 0x05}, {0x6d, 0x20},
> +	{0x6e, 0xa0}
> +};
> +
> +static const u8 pal_nc_values[][2] = {
> +	{0x28, 33}, {0x29, 37}, {0x5a, 0x00}, {0x5b, 125},
> +	{0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0xc0+53},
> +	{0x61, 0x06}, {0x62, 47}, {0x63, 0x46}, {0x64, 0x94},
> +	{0x65, 0xf6}, {0x66, 0x21}, {0x6c, 0x05}, {0x6d, 0x20},
> +	{0x6e, 0xa0}
> +};
> +
> +static const u8 pal_m_values[][2] = {
> +	{0x28, 25}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 118},
> +	{0x5c, 165}, {0x5d, 45}, {0x5e, 49}, {0x5f, 0xc0+59},
> +	{0x61, 0x17}, {0x62, 45}, {0x63, 0xe3}, {0x64, 0xef},
> +	{0x65, 0xe6}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
> +	{0x6e, 0xa0}
> +};
> +
> +static const u8 ntsc_values[][2] = {
> +	{0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
> +	{0x5c, 165}, {0x5d, 42}, {0x5e, 46}, {0x5f, 0xc0+46},
> +	{0x61, 0x15}, {0x62, 63}, {0x63, 0x1f}, {0x64, 0x7c},
> +	{0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
> +	{0x6e, 0x80}
> +};
> +
> +static const u8 ntsc_jp_values[][2] = {
> +	{0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
> +	{0x5c, 165}, {0x5d, 19}, {0x5e, 46}, {0x5f, 0xc0+46},
> +	{0x61, 0x05}, {0x62, 62}, {0x63, 0x1f}, {0x64, 0x7c},
> +	{0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
> +	{0x6e, 0x80}
> +};
> +
> +struct saa7121 {
> +	int std;
> +	int yc;
> +	struct v4l2_pix_format fmt;
> +	struct v4l2_rect crop;
> +	u8 regs[128];
> +};
> +
> +static int saa7121_write_regs(struct i2c_client *client)
> +{
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(me->regs); i++) {
> +		int ret = i2c_smbus_write_byte_data(client, i, me->regs[i]);
> +		if (ret < 0)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +static void saa7121_change_regs(struct saa7121 *me, const u8 (*regs)[2],
> +				int num)
> +{
> +	int i;
> +	for (i = 0; i < num; i++)
> +		me->regs[regs[i][0]] = regs[i][1];
> +}
> +
> +static const struct {
> +	v4l2_std_id mask;
> +	const char *name;
> +	const u8 (*regs)[2];
> +	int num;
> +} standards[] = {
> +	{
> +		V4L2_STD_PAL | V4L2_STD_PAL_N,
> +		"PAL",
> +		pal_values,
> +		ARRAY_SIZE(pal_values)
> +	},
> +	{
> +		V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
> +		"NTSC",
> +		ntsc_values,
> +		ARRAY_SIZE(ntsc_values)
> +	},
> +	{
> +		V4L2_STD_NTSC_M_JP,
> +		"NTSC-JP",
> +		ntsc_jp_values,
> +		ARRAY_SIZE(ntsc_jp_values)
> +	},
> +	{
> +		V4L2_STD_PAL_M,
> +		"PAL-M",
> +		pal_m_values,
> +		ARRAY_SIZE(pal_nc_values)
> +	},
> +	{
> +		V4L2_STD_PAL_Nc,
> +		"PAL-Nc",
> +		pal_nc_values,
> +		ARRAY_SIZE(pal_nc_values)
> +	},
> +};
> +
> +static int saa7121_e_std(void *ctx, struct v4l2_standard *std)
> +{
> +	if (std->index >= ARRAY_SIZE(standards))
> +		return -EINVAL;
> +	std->id = standards[std->index].mask;
> +	strcpy(std->name, standards[std->index].name);
> +	if (std->id & V4L2_STD_625_50) {
> +		std->frameperiod.numerator = 1;
> +		std->frameperiod.denominator = 25;
> +		std->framelines = 625;
> +	} else  {
> +		std->frameperiod.numerator = 1001;
> +		std->frameperiod.denominator = 30000;
> +		std->framelines = 525;
> +	}
> +	return 0;
> +}
> +
> +static int saa7121_cropcap(void *ctx, struct v4l2_cropcap *cap)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +
> +	if (standards[me->std].mask & V4L2_STD_625_50) {
> +		cap->bounds.top = 23 * 2;
> +		cap->bounds.left = 132;
> +		cap->bounds.width = 720;
> +		cap->bounds.height = 576;
> +		cap->defrect.left = 140;
> +		cap->defrect.top = 23 * 2;
> +		cap->defrect.width = 702;
> +		cap->defrect.height = 576;
> +		cap->pixelaspect.numerator = 54;
> +		cap->pixelaspect.denominator = 59;
> +	} else {
> +		cap->bounds.top = 20 * 2;
> +		cap->bounds.left = 122;
> +		cap->bounds.width = 720;
> +		cap->bounds.height = 487;
> +		cap->defrect.left = 130;
> +		cap->defrect.top = 22 * 2;
> +		cap->defrect.width = 704;
> +		cap->defrect.height = 480;
> +		cap->pixelaspect.numerator = 11;
> +		cap->pixelaspect.denominator = 10;
> +	}
> +	return 0;
> +}
> +
> +static int saa7121_s_crop(void *ctx, struct v4l2_crop *crop, int busy)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +	struct v4l2_cropcap cap;
> +
> +	if (busy)
> +		return -EBUSY;
> +
> +	saa7121_cropcap(ctx, &cap);
> +	me->crop = crop->c;
> +
> +	if (me->crop.width > cap.bounds.width)
> +		me->crop.width = cap.bounds.width;
> +	me->crop.left += me->crop.width & 1;
> +	me->crop.width &= ~1;
> +	if (me->crop.height > cap.bounds.height)
> +		me->crop.height = cap.bounds.height;
> +	if (me->crop.left < cap.bounds.left)
> +		me->crop.left = cap.bounds.left;
> +	if (me->crop.left > cap.bounds.left + cap.bounds.width - me->crop.width)
> +		me->crop.left = cap.bounds.left + cap.bounds.width
> +				 - me->crop.width;
> +	me->crop.left &= ~1;
> +	if (me->crop.top < cap.bounds.top)
> +		me->crop.top = cap.bounds.top;
> +	if (me->crop.top > cap.bounds.top + cap.bounds.height - me->crop.height)
> +		me->crop.top = cap.bounds.top + cap.bounds.height
> +				- me->crop.height;
> +	me->crop.top &= ~1;
> +	me->fmt.width = me->crop.width;
> +	me->fmt.height = me->crop.height;
> +	return 0;
> +}
> +
> +static int saa7121_g_crop(void *ctx, struct v4l2_crop *crop)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +
> +	crop->c = me->crop;
> +	return 0;
> +}
> +
> +static int saa7121_s_fmt(void *ctx, int try_fmt, struct v4l2_pix_format
> *fmt,
> +			 int busy)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +	struct v4l2_cropcap cap;
> +
> +	if (!try_fmt && busy)
> +		return -EBUSY;
> +
> +	saa7121_cropcap(ctx, &cap);
> +	fmt->pixelformat = V4L2_PIX_FMT_UYVY;
> +	fmt->field = V4L2_FIELD_ALTERNATE;
> +
> +	if (standards[me->std].mask & V4L2_STD_625_50)
> +		fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
> +	else if (standards[me->std].mask == V4L2_STD_PAL_M)
> +		fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
> +	else
> +		fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
> +
> +	fmt->width &= ~1;
> +	if (fmt->width > cap.bounds.width)
> +		fmt->width = cap.bounds.width;
> +	if (fmt->height > cap.bounds.height)
> +		fmt->height = cap.bounds.height;
> +
> +	if (!try_fmt) {
> +		me->fmt = *fmt;
> +		me->crop.width = fmt->width;
> +		me->crop.height = fmt->height;
> +		if (me->crop.left > cap.bounds.left + cap.bounds.width
> +				     - fmt->width) {
> +			me->crop.left = cap.bounds.left + cap.bounds.width
> +					 - fmt->width;
> +			me->crop.left &= ~1;
> +		}
> +		if (me->crop.top > cap.bounds.top + cap.bounds.height
> +				    - fmt->height) {
> +			me->crop.top = cap.bounds.top + cap.bounds.height
> +					- fmt->height;
> +			me->crop.top &= ~1;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int saa7121_g_fmt(void *ctx, struct v4l2_pix_format *fmt)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +	*fmt = me->fmt;
> +	return 0;
> +}
> +
> +static void saa7121_g_mode(void *ctx, struct s6dp_mode *mode)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +	struct v4l2_cropcap cap;
> +
> +	saa7121_cropcap(ctx, &cap);
> +	mode->type = S6_DP_VIDEO_CFG_MODE_422_SERIAL;
> +	mode->progressive = 0;
> +	mode->embedded_sync = 1;
> +	mode->relaxed_framing = 0;
> +	mode->ten_bit = 0;
> +	mode->line_and_crc = 0;
> +	mode->micron_mode = 0;
> +	if (standards[me->std].mask & V4L2_STD_625_50) {
> +		mode->pixel_total = 864;
> +		mode->framelines = 625;
> +		mode->odd_vsync_offset = 623;
> +		mode->odd_vsync_len = 24;
> +		mode->odd_first = 22;
> +		mode->odd_total = 312;
> +		mode->even_vsync_offset = 310;
> +		mode->even_vsync_len = 25;
> +		mode->even_first = 335;
> +		mode->hsync_offset = 0;
> +		mode->hsync_len = 4;
> +	} else {
> +		mode->pixel_total = 858;
> +		mode->framelines = 525;
> +		mode->odd_vsync_offset = 522;
> +		mode->odd_vsync_len = 19;
> +		mode->odd_first = 16;
> +		mode->odd_total = 262;
> +		mode->even_vsync_offset = 260;
> +		mode->even_vsync_len = 19;
> +		mode->even_first = 279;
> +		mode->hsync_offset = 0;
> +		mode->hsync_len = 4;
> +	}
> +	mode->even_active = me->crop.height / 2;
> +	mode->odd_active = me->crop.height - mode->even_active;
> +	mode->odd_first += (me->crop.top - cap.bounds.top) / 2;
> +	mode->even_first += (me->crop.top - cap.bounds.top) / 2;
> +	mode->pixel_active = me->crop.width;
> +	mode->pixel_offset = me->crop.left - cap.bounds.left;
> +	mode->pixel_padding = 720 - mode->pixel_active - mode->pixel_offset;
> +}
> +
> +static void saa7121_reconfigure(struct i2c_client *client)
> +{
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +
> +	saa7121_change_regs(me, standards[me->std].regs,
> +			    standards[me->std].num);
> +	if (me->yc)
> +		me->regs[0x5f] &= 0x3f;
> +
> +	saa7121_write_regs(client);
> +}
> +
> +static int saa7121_s_std(void *ctx, v4l2_std_id *mask, int busy)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +	int i;
> +
> +	if (busy && !(standards[me->std].mask & V4L2_STD_625_50)
> +		     == !(*mask & V4L2_STD_625_50))
> +		return -EBUSY;
> +
> +	for (i = 0; i < ARRAY_SIZE(standards); i++) {
> +		if (standards[i].mask & *mask) {
> +			me->std = i;
> +			saa7121_reconfigure(client);
> +			*mask = standards[i].mask;
> +			saa7121_s_fmt(ctx, 0, &me->fmt, 0);
> +			return 0;
> +		}
> +	}
> +	return -EINVAL;
> +}
> +
> +static int saa7121_e_outp(void *ctx, struct v4l2_output *outp)
> +{
> +	int i;
> +	if (outp->index > 1)
> +		return -EINVAL;
> +
> +	outp->type = V4L2_OUTPUT_TYPE_ANALOG;
> +	for (i = 0; i < ARRAY_SIZE(standards); i++)
> +		outp->std |= standards[i].mask;
> +
> +	strcpy(outp->name, outp->index ? "Y/C" : "CVBS");
> +	return 0;
> +}
> +
> +static int saa7121_s_outp(void *ctx, unsigned int nr, int busy)
> +{
> +	struct i2c_client *client = ctx;
> +	struct saa7121 *me = i2c_get_clientdata(client);
> +
> +	if (nr > 1)
> +		return -EINVAL;
> +
> +	/*
> +	 * both outputs are always active
> +	 * we just disable the cross color filter for Y/C
> +	 */
> +	me->yc = nr;
> +	saa7121_reconfigure(client);
> +	return 0;
> +}
> +
> +static int saa7121_probe(struct i2c_client *client,
> +			 const struct i2c_device_id *id)
> +{
> +	struct saa7121 *me;
> +	struct s6dp_link *link;
> +	s32 val;
> +
> +	if (!client->dev.platform_data)
> +		return -EINVAL;
> +	link = client->dev.platform_data;
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE
> +					| I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> +		return -ENODEV;
> +
> +	val = i2c_smbus_read_byte(client);
> +	if (val < 0) {
> +		printk(KERN_ERR "saa7121: can't read status byte\n");
> +		return -EIO;
> +	}
> +	if ((val & 0xE0) != 0x20) {
> +		printk(KERN_ERR "saa7121: unsupported chip version %i"
> +				" (status = 0x%02x)\n", val >> 5, val);
> +		return -ENODEV;
> +	}
> +	me = kzalloc(sizeof(*me), GFP_KERNEL);
> +	if (!me)
> +		return -ENOMEM;
> +	me->std = V4L2_STD_PAL;
> +	i2c_set_clientdata(client, me);
> +
> +	saa7121_change_regs(me, initial_setup, ARRAY_SIZE(initial_setup));
> +	saa7121_change_regs(me, pal_values, ARRAY_SIZE(pal_values));
> +	if (saa7121_write_regs(client) < 0) {
> +		printk(KERN_ERR "saa7121: can't write registers\n");
> +		kfree(me);
> +		return -EIO;
> +	}
> +
> +	link->g_mode = saa7121_g_mode;
> +	link->e_std = saa7121_e_std;
> +	link->s_std = saa7121_s_std;
> +	link->s_fmt = saa7121_s_fmt;
> +	link->g_fmt = saa7121_g_fmt;
> +	link->cropcap = saa7121_cropcap;
> +	link->s_crop = saa7121_s_crop;
> +	link->g_crop = saa7121_g_crop;
> +	link->dir.egress.e_outp = saa7121_e_outp;
> +	link->dir.egress.s_outp = saa7121_s_outp;
> +	link->context = client;
> +	printk(KERN_INFO "saa7121 probed successfully\n");
> +	return 0;
> +}
> +
> +static int saa7121_remove(struct i2c_client *client)
> +{
> +	struct saa7121_data *data;
> +	data = i2c_get_clientdata(client);
> +	i2c_set_clientdata(client, NULL);
> +	kfree(data);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id saa7121_id[] = {
> +	{ "saa7121", 0 },
> +	{ }
> +};
> +
> +static struct i2c_driver saa7121_driver = {
> +	.driver = {
> +		.name   = "s6dp-saa7121",
> +	},
> +	.probe          = saa7121_probe,
> +	.remove         = saa7121_remove,
> +	.id_table	= saa7121_id,
> +};
> +
> +static int __init saa7121_init(void)
> +{
> +	return i2c_add_driver(&saa7121_driver);
> +}
> +
> +static void __exit saa7121_exit(void)
> +{
> +	i2c_del_driver(&saa7121_driver);
> +}
> +
> +MODULE_AUTHOR("Daniel Gloeckner <dg@emlix.com>");
> +MODULE_DESCRIPTION("SAA7121 driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(saa7121_init);
> +module_exit(saa7121_exit);
> --
> 1.6.2.107.ge47ee
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG


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

end of thread, other threads:[~2009-03-30 13:42 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
2009-03-26 14:36 ` [patch 2/5] s6000 data port: custom video mode support Daniel Glöckner
2009-03-26 14:36 ` [patch 3/5] s6000 data port: canonical modes Daniel Glöckner
2009-03-26 14:36 ` [patch 4/5] mt9d131 driver for s6000 data port Daniel Glöckner
2009-03-26 14:36 ` [patch 5/5] saa7121 " Daniel Glöckner
2009-03-26 15:08 Hans Verkuil
2009-03-30  9:56 ` Daniel Glöckner
2009-03-30 10:03   ` Hans Verkuil
2009-03-30 12:12     ` Daniel Glöckner
2009-03-30 12:50       ` Hans Verkuil
2009-03-30 13:36         ` Daniel Glöckner
2009-03-30 13:41           ` Hans Verkuil

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.