From: Oskar Schirmer <os@xxxxxxxxx> 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@xxxxxxxxx> Signed-off-by: Oskar Schirmer <os@xxxxxxxxx> Signed-off-by: Johannes Weiner <jw@xxxxxxxxx> Signed-off-by: Daniel Glöckner <dg@xxxxxxxxx> --- 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@xxxxxxxxx> + * Oskar Schirmer <os@xxxxxxxxx> + * Johannes Weiner <jw@xxxxxxxxx> + * Daniel Gloeckner <dg@xxxxxxxxx> + * + * 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@xxxxxxxxx>\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@xxxxxxxxx> + * Authors: Fabian Godehardt <fg@xxxxxxxxx> + * Oskar Schirmer <os@xxxxxxxxx> + */ + +#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 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html