V4L/DVB (11066): au0828: add support for analog functionality in bridge
Devin Heitmueller [Wed, 11 Mar 2009 06:00:40 +0000 (03:00 -0300)]
Add support for the analog functionality found in the au0828 bridge

Thanks to Michael Krufky <mkrufky@linuxtv.org> and Steven Toth
<stoth@linuxtv.org> for providing sample hardware, engineering level support,
and testing.

Signed-off-by: Devin Heitmueller <dheitmueller@linuxtv.org>
Signed-off-by: Michael Krufky <mkrufky@linuxtv.org>
[mchehab@redhat.com: fix compilation by adding linux/version.h]
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

drivers/media/video/au0828/Makefile
drivers/media/video/au0828/au0828-cards.c
drivers/media/video/au0828/au0828-core.c
drivers/media/video/au0828/au0828-reg.h
drivers/media/video/au0828/au0828-video.c [new file with mode: 0644]
drivers/media/video/au0828/au0828.h

index cd2c582..4d26231 100644 (file)
@@ -1,4 +1,4 @@
-au0828-objs    := au0828-core.o au0828-i2c.o au0828-cards.o au0828-dvb.o
+au0828-objs    := au0828-core.o au0828-i2c.o au0828-cards.o au0828-dvb.o au0828-video.o
 
 obj-$(CONFIG_VIDEO_AU0828) += au0828.o
 
index d60123b..aa79e6b 100644 (file)
 
 #include "au0828.h"
 #include "au0828-cards.h"
+#include "au8522.h"
+
+void hvr950q_cs5340_audio(void *priv, int enable)
+{
+       /* Because the HVR-950q shares an i2s bus between the cs5340 and the
+          au8522, we need to hold cs5340 in reset when using the au8522 */
+       struct au0828_dev *dev = priv;
+       if (enable == 1)
+               au0828_set(dev, REG_000, 0x10);
+       else
+               au0828_clear(dev, REG_000, 0x10);
+}
 
 struct au0828_board au0828_boards[] = {
        [AU0828_BOARD_UNKNOWN] = {
@@ -31,6 +43,25 @@ struct au0828_board au0828_boards[] = {
        },
        [AU0828_BOARD_HAUPPAUGE_HVR950Q] = {
                .name   = "Hauppauge HVR950Q",
+               .input = {
+                       {
+                               .type = AU0828_VMUX_TELEVISION,
+                               .vmux = AU8522_COMPOSITE_CH4_SIF,
+                               .amux = AU8522_AUDIO_SIF,
+                       },
+                       {
+                               .type = AU0828_VMUX_COMPOSITE,
+                               .vmux = AU8522_COMPOSITE_CH1,
+                               .amux = AU8522_AUDIO_NONE,
+                               .audio_setup = hvr950q_cs5340_audio,
+                       },
+                       {
+                               .type = AU0828_VMUX_SVIDEO,
+                               .vmux = AU8522_SVIDEO_CH13,
+                               .amux = AU8522_AUDIO_NONE,
+                               .audio_setup = hvr950q_cs5340_audio,
+                       },
+               },
        },
        [AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL] = {
                .name   = "Hauppauge HVR950Q rev xxF8",
@@ -144,21 +175,23 @@ void au0828_gpio_setup(struct au0828_dev *dev)
                 * 4 - CS5340
                 * 5 - AU8522 Demodulator
                 * 6 - eeprom W/P
+                * 7 - power supply
                 * 9 - XC5000 Tuner
                 */
 
                /* Into reset */
                au0828_write(dev, REG_003, 0x02);
-               au0828_write(dev, REG_002, 0x88 | 0x20);
+               au0828_write(dev, REG_002, 0x80 | 0x20 | 0x10);
                au0828_write(dev, REG_001, 0x0);
                au0828_write(dev, REG_000, 0x0);
                msleep(100);
 
-               /* Out of reset */
+               /* Out of reset (leave the cs5340 in reset until needed) */
                au0828_write(dev, REG_003, 0x02);
                au0828_write(dev, REG_001, 0x02);
-               au0828_write(dev, REG_002, 0x88 | 0x20);
-               au0828_write(dev, REG_000, 0x88 | 0x20 | 0x40);
+               au0828_write(dev, REG_002, 0x80 | 0x20 | 0x10);
+               au0828_write(dev, REG_000, 0x80 | 0x40 | 0x20);
+
                msleep(250);
                break;
        case AU0828_BOARD_DVICO_FUSIONHDTV7:
index 5765e86..680e88f 100644 (file)
@@ -146,6 +146,8 @@ static void au0828_usb_disconnect(struct usb_interface *interface)
        /* Digital TV */
        au0828_dvb_unregister(dev);
 
+       au0828_analog_unregister(dev);
+
        /* I2C */
        au0828_i2c_unregister(dev);
 
@@ -162,9 +164,11 @@ static void au0828_usb_disconnect(struct usb_interface *interface)
 static int au0828_usb_probe(struct usb_interface *interface,
        const struct usb_device_id *id)
 {
-       int ifnum;
+       int ifnum, i;
        struct au0828_dev *dev;
        struct usb_device *usbdev = interface_to_usbdev(interface);
+       struct usb_host_interface *iface_desc;
+       struct usb_endpoint_descriptor *endpoint;
 
        ifnum = interface->altsetting->desc.bInterfaceNumber;
 
@@ -189,6 +193,30 @@ static int au0828_usb_probe(struct usb_interface *interface,
 
        usb_set_intfdata(interface, dev);
 
+       /* set au0828 usb interface0 to as5 */
+       usb_set_interface(usbdev,
+                         interface->cur_altsetting->desc.bInterfaceNumber, 5);
+
+       /* Figure out which endpoint has the isoc interface */
+       iface_desc = interface->cur_altsetting;
+       for(i = 0; i < iface_desc->desc.bNumEndpoints; i++){
+               endpoint = &iface_desc->endpoint[i].desc;
+               if(((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) &&
+                  ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC)){
+
+                       /* we find our isoc in endpoint */
+                       u16 tmp = le16_to_cpu(endpoint->wMaxPacketSize);
+                       dev->max_pkt_size = (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+                       dev->isoc_in_endpointaddr = endpoint->bEndpointAddress;
+               }
+       }
+       if(!(dev->isoc_in_endpointaddr)) {
+               printk("Could not locate isoc endpoint\n");
+               kfree(dev);
+               return -ENODEV;
+       }
+
+
        /* Power Up the bridge */
        au0828_write(dev, REG_600, 1 << 4);
 
@@ -201,6 +229,9 @@ static int au0828_usb_probe(struct usb_interface *interface,
        /* Setup */
        au0828_card_setup(dev);
 
+       /* Analog TV */
+       au0828_analog_register(dev);
+
        /* Digital TV */
        au0828_dvb_register(dev);
 
index 1e87fa0..b15e4a3 100644 (file)
@@ -27,6 +27,9 @@
 #define REG_002 0x002
 #define REG_003 0x003
 
+#define AU0828_SENSORCTRL_100 0x100
+#define AU0828_SENSORCTRL_VBI_103 0x103
+
 #define REG_200 0x200
 #define REG_201 0x201
 #define REG_202 0x202
@@ -35,4 +38,7 @@
 #define REG_209 0x209
 #define REG_2FF 0x2ff
 
+/* Audio registers */
+#define AU0828_AUDIOCTRL_50C 0x50C
+
 #define REG_600 0x600
diff --git a/drivers/media/video/au0828/au0828-video.c b/drivers/media/video/au0828/au0828-video.c
new file mode 100644 (file)
index 0000000..e34464f
--- /dev/null
@@ -0,0 +1,1677 @@
+/*
+ * Auvitek AU0828 USB Bridge (Analog video support)
+ *
+ * Copyright (C) 2009 Devin Heitmueller <dheitmueller@linuxtv.org>
+ * Copyright (C) 2005-2008 Auvitek International, Ltd.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* Developer Notes:
+ *
+ * VBI support is not yet working
+ * The hardware scaler supported is unimplemented
+ * AC97 audio support is unimplemented (only i2s audio mode)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/suspend.h>
+#include <linux/version.h>
+#include <linux/videodev.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-chip-ident.h>
+#include <media/tuner.h>
+#include "au0828.h"
+#include "au0828-reg.h"
+
+static LIST_HEAD(au0828_devlist);
+static DEFINE_MUTEX(au0828_sysfs_lock);
+
+#define AU0828_VERSION_CODE KERNEL_VERSION(0, 0, 1)
+
+/* Forward declarations */
+void au0828_analog_stream_reset(struct au0828_dev *dev);
+
+/* ------------------------------------------------------------------
+       Videobuf operations
+   ------------------------------------------------------------------*/
+
+static unsigned int isoc_debug;
+module_param(isoc_debug, int, 0644);
+MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
+
+#define au0828_isocdbg(fmt, arg...) \
+do {\
+       if (isoc_debug) { \
+               printk(KERN_INFO "au0828 %s :"fmt, \
+                      __func__ , ##arg);          \
+       } \
+  } while (0)
+
+static inline void print_err_status(struct au0828_dev *dev,
+                                   int packet, int status)
+{
+       char *errmsg = "Unknown";
+
+       switch (status) {
+       case -ENOENT:
+               errmsg = "unlinked synchronuously";
+               break;
+       case -ECONNRESET:
+               errmsg = "unlinked asynchronuously";
+               break;
+       case -ENOSR:
+               errmsg = "Buffer error (overrun)";
+               break;
+       case -EPIPE:
+               errmsg = "Stalled (device not responding)";
+               break;
+       case -EOVERFLOW:
+               errmsg = "Babble (bad cable?)";
+               break;
+       case -EPROTO:
+               errmsg = "Bit-stuff error (bad cable?)";
+               break;
+       case -EILSEQ:
+               errmsg = "CRC/Timeout (could be anything)";
+               break;
+       case -ETIME:
+               errmsg = "Device does not respond";
+               break;
+       }
+       if (packet < 0) {
+               au0828_isocdbg("URB status %d [%s].\n", status, errmsg);
+       } else {
+               au0828_isocdbg("URB packet %d, status %d [%s].\n",
+                              packet, status, errmsg);
+       }
+}
+
+static int check_dev(struct au0828_dev *dev)
+{
+       if (dev->dev_state & DEV_DISCONNECTED) {
+               printk("v4l2 ioctl: device not present\n");
+               return -ENODEV;
+       }
+
+       if (dev->dev_state & DEV_MISCONFIGURED) {
+               printk("v4l2 ioctl: device is misconfigured; "
+                      "close and open it again\n");
+               return -EIO;
+       }
+       return 0;
+}
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void au0828_irq_callback(struct urb *urb)
+{
+       struct au0828_dmaqueue  *dma_q = urb->context;
+       struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vidq);
+       int rc, i;
+
+       switch (urb->status) {
+       case 0:             /* success */
+       case -ETIMEDOUT:    /* NAK */
+               break;
+       case -ECONNRESET:   /* kill */
+       case -ENOENT:
+       case -ESHUTDOWN:
+               au0828_isocdbg("au0828_irq_callback called: status kill\n");
+               return;
+       default:            /* unknown error */
+               au0828_isocdbg("urb completition error %d.\n", urb->status);
+               break;
+       }
+
+       /* Copy data from URB */
+       spin_lock(&dev->slock);
+       rc = dev->isoc_ctl.isoc_copy(dev, urb);
+       spin_unlock(&dev->slock);
+
+       /* Reset urb buffers */
+       for (i = 0; i < urb->number_of_packets; i++) {
+               urb->iso_frame_desc[i].status = 0;
+               urb->iso_frame_desc[i].actual_length = 0;
+       }
+       urb->status = 0;
+
+       urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+       if (urb->status) {
+               au0828_isocdbg("urb resubmit failed (error=%i)\n",
+                              urb->status);
+       }
+}
+
+/*
+ * Stop and Deallocate URBs
+ */
+void au0828_uninit_isoc(struct au0828_dev *dev)
+{
+       struct urb *urb;
+       int i;
+
+       au0828_isocdbg("au0828: called au0828_uninit_isoc\n");
+
+       dev->isoc_ctl.nfields = -1;
+       for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+               urb = dev->isoc_ctl.urb[i];
+               if (urb) {
+                       if (!irqs_disabled())
+                               usb_kill_urb(urb);
+                       else
+                               usb_unlink_urb(urb);
+
+                       if (dev->isoc_ctl.transfer_buffer[i]) {
+                               usb_buffer_free(dev->usbdev,
+                                       urb->transfer_buffer_length,
+                                       dev->isoc_ctl.transfer_buffer[i],
+                                       urb->transfer_dma);
+                       }
+                       usb_free_urb(urb);
+                       dev->isoc_ctl.urb[i] = NULL;
+               }
+               dev->isoc_ctl.transfer_buffer[i] = NULL;
+       }
+
+       kfree(dev->isoc_ctl.urb);
+       kfree(dev->isoc_ctl.transfer_buffer);
+
+       dev->isoc_ctl.urb = NULL;
+       dev->isoc_ctl.transfer_buffer = NULL;
+       dev->isoc_ctl.num_bufs = 0;
+}
+
+/*
+ * Allocate URBs and start IRQ
+ */
+int au0828_init_isoc(struct au0828_dev *dev, int max_packets,
+                    int num_bufs, int max_pkt_size,
+                    int (*isoc_copy) (struct au0828_dev *dev, struct urb *urb))
+{
+       struct au0828_dmaqueue *dma_q = &dev->vidq;
+       int i;
+       int sb_size, pipe;
+       struct urb *urb;
+       int j, k;
+       int rc;
+
+       au0828_isocdbg("au0828: called au0828_prepare_isoc\n");
+
+       /* De-allocates all pending stuff */
+       au0828_uninit_isoc(dev);
+
+       dev->isoc_ctl.isoc_copy = isoc_copy;
+       dev->isoc_ctl.num_bufs = num_bufs;
+
+       dev->isoc_ctl.urb = kzalloc(sizeof(void *)*num_bufs,  GFP_KERNEL);
+       if (!dev->isoc_ctl.urb) {
+               au0828_isocdbg("cannot alloc memory for usb buffers\n");
+               return -ENOMEM;
+       }
+
+       dev->isoc_ctl.transfer_buffer = kzalloc(sizeof(void *)*num_bufs,
+                                             GFP_KERNEL);
+       if (!dev->isoc_ctl.transfer_buffer) {
+               au0828_isocdbg("cannot allocate memory for usb transfer\n");
+               kfree(dev->isoc_ctl.urb);
+               return -ENOMEM;
+       }
+
+       dev->isoc_ctl.max_pkt_size = max_pkt_size;
+       dev->isoc_ctl.buf = NULL;
+
+       sb_size = max_packets * dev->isoc_ctl.max_pkt_size;
+
+       /* allocate urbs and transfer buffers */
+       for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+               urb = usb_alloc_urb(max_packets, GFP_KERNEL);
+               if (!urb) {
+                       au0828_isocdbg("cannot alloc isoc_ctl.urb %i\n", i);
+                       au0828_uninit_isoc(dev);
+                       return -ENOMEM;
+               }
+               dev->isoc_ctl.urb[i] = urb;
+
+               dev->isoc_ctl.transfer_buffer[i] = usb_buffer_alloc(dev->usbdev,
+                       sb_size, GFP_KERNEL, &urb->transfer_dma);
+               if (!dev->isoc_ctl.transfer_buffer[i]) {
+                       printk("unable to allocate %i bytes for transfer"
+                                       " buffer %i%s\n",
+                                       sb_size, i,
+                                       in_interrupt() ? " while in int" : "");
+                       au0828_uninit_isoc(dev);
+                       return -ENOMEM;
+               }
+               memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size);
+
+               pipe = usb_rcvisocpipe(dev->usbdev,
+                                      dev->isoc_in_endpointaddr),
+
+               usb_fill_int_urb(urb, dev->usbdev, pipe,
+                                dev->isoc_ctl.transfer_buffer[i], sb_size,
+                                au0828_irq_callback, dma_q, 1);
+
+               urb->number_of_packets = max_packets;
+               urb->transfer_flags = URB_ISO_ASAP;
+
+               k = 0;
+               for (j = 0; j < max_packets; j++) {
+                       urb->iso_frame_desc[j].offset = k;
+                       urb->iso_frame_desc[j].length =
+                                               dev->isoc_ctl.max_pkt_size;
+                       k += dev->isoc_ctl.max_pkt_size;
+               }
+       }
+
+       init_waitqueue_head(&dma_q->wq);
+
+       /* submit urbs and enables IRQ */
+       for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+               rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC);
+               if (rc) {
+                       au0828_isocdbg("submit of urb %i failed (error=%i)\n",
+                                      i, rc);
+                       au0828_uninit_isoc(dev);
+                       return rc;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Announces that a buffer were filled and request the next
+ */
+static inline void buffer_filled(struct au0828_dev *dev,
+                                 struct au0828_dmaqueue *dma_q,
+                                 struct au0828_buffer *buf)
+{
+       /* Advice that buffer was filled */
+       au0828_isocdbg("[%p/%d] wakeup\n", buf, buf->vb.i);
+
+       buf->vb.state = VIDEOBUF_DONE;
+       buf->vb.field_count++;
+       do_gettimeofday(&buf->vb.ts);
+
+       dev->isoc_ctl.buf = NULL;
+
+       list_del(&buf->vb.queue);
+       wake_up(&buf->vb.done);
+}
+
+/*
+ * Identify the buffer header type and properly handles
+ */
+static void au0828_copy_video(struct au0828_dev *dev,
+                             struct au0828_dmaqueue  *dma_q,
+                             struct au0828_buffer *buf,
+                             unsigned char *p,
+                             unsigned char *outp, unsigned long len)
+{
+       void *fieldstart, *startwrite, *startread;
+       int  linesdone, currlinedone, offset, lencopy, remain;
+       int bytesperline = dev->width << 1; /* Assumes 16-bit depth @@@@ */
+
+       if (dma_q->pos + len > buf->vb.size)
+               len = buf->vb.size - dma_q->pos;
+
+       startread = p;
+       remain = len;
+
+       /* Interlaces frame */
+       if (buf->top_field)
+               fieldstart = outp;
+       else
+               fieldstart = outp + bytesperline;
+
+       linesdone = dma_q->pos / bytesperline;
+       currlinedone = dma_q->pos % bytesperline;
+       offset = linesdone * bytesperline * 2 + currlinedone;
+       startwrite = fieldstart + offset;
+       lencopy = bytesperline - currlinedone;
+       lencopy = lencopy > remain ? remain : lencopy;
+
+       if ((char *)startwrite + lencopy > (char *)outp + buf->vb.size) {
+               au0828_isocdbg("Overflow of %zi bytes past buffer end (1)\n",
+                              ((char *)startwrite + lencopy) -
+                              ((char *)outp + buf->vb.size));
+               remain = (char *)outp + buf->vb.size - (char *)startwrite;
+               lencopy = remain;
+       }
+       if (lencopy <= 0)
+               return;
+       memcpy(startwrite, startread, lencopy);
+
+       remain -= lencopy;
+
+       while (remain > 0) {
+               startwrite += lencopy + bytesperline;
+               startread += lencopy;
+               if (bytesperline > remain)
+                       lencopy = remain;
+               else
+                       lencopy = bytesperline;
+
+               if ((char *)startwrite + lencopy > (char *)outp +
+                   buf->vb.size) {
+                       au0828_isocdbg("Overflow of %zi bytes past buffer end (2)\n",
+                                      ((char *)startwrite + lencopy) -
+                                      ((char *)outp + buf->vb.size));
+                       lencopy = remain = (char *)outp + buf->vb.size -
+                                          (char *)startwrite;
+               }
+               if (lencopy <= 0)
+                       break;
+
+               memcpy(startwrite, startread, lencopy);
+
+               remain -= lencopy;
+       }
+
+       if (offset > 1440) {
+               /* We have enough data to check for greenscreen */
+               if (outp[0] < 0x60 && outp[1440] < 0x60) {
+                       dev->greenscreen_detected = 1;
+               }
+       }
+
+       dma_q->pos += len;
+}
+
+/*
+ * video-buf generic routine to get the next available buffer
+ */
+static inline void get_next_buf(struct au0828_dmaqueue *dma_q,
+                               struct au0828_buffer **buf)
+{
+       struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vidq);
+
+       if (list_empty(&dma_q->active)) {
+               au0828_isocdbg("No active queue to serve\n");
+               dev->isoc_ctl.buf = NULL;
+               *buf = NULL;
+               return;
+       }
+
+       /* Get the next buffer */
+       *buf = list_entry(dma_q->active.next, struct au0828_buffer, vb.queue);
+       dev->isoc_ctl.buf = *buf;
+
+       return;
+}
+
+/*
+ * Controls the isoc copy of each urb packet
+ */
+static inline int au0828_isoc_copy(struct au0828_dev *dev, struct urb *urb)
+{
+       struct au0828_buffer    *buf;
+       struct au0828_dmaqueue  *dma_q = urb->context;
+       unsigned char *outp = NULL;
+       int i, len = 0, rc = 1;
+       unsigned char *p;
+       unsigned char fbyte;
+
+       if (!dev)
+               return 0;
+
+       if ((dev->dev_state & DEV_DISCONNECTED) ||
+           (dev->dev_state & DEV_MISCONFIGURED))
+               return 0;
+
+       if (urb->status < 0) {
+               print_err_status(dev, -1, urb->status);
+               if (urb->status == -ENOENT)
+                       return 0;
+       }
+
+       buf = dev->isoc_ctl.buf;
+       if (buf != NULL)
+               outp = videobuf_to_vmalloc(&buf->vb);
+
+       for (i = 0; i < urb->number_of_packets; i++) {
+               int status = urb->iso_frame_desc[i].status;
+
+               if (status < 0) {
+                       print_err_status(dev, i, status);
+                       if (urb->iso_frame_desc[i].status != -EPROTO)
+                               continue;
+               }
+
+               if (urb->iso_frame_desc[i].actual_length <= 0) {
+                       continue;
+               }
+               if (urb->iso_frame_desc[i].actual_length >
+                                               dev->max_pkt_size) {
+                       au0828_isocdbg("packet bigger than packet size");
+                       continue;
+               }
+
+               p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+               fbyte = p[0];
+               len = urb->iso_frame_desc[i].actual_length - 4;
+               p += 4;
+
+               if (fbyte & 0x80) {
+                       len -= 4;
+                       p += 4;
+                       au0828_isocdbg("Video frame %s\n",
+                                      (fbyte & 0x40) ? "odd" : "even");
+                       if (!(fbyte & 0x40)) {
+                               if (buf != NULL)
+                                       buffer_filled(dev, dma_q, buf);
+                               get_next_buf(dma_q, &buf);
+                               if (buf == NULL) {
+                                       outp = NULL;
+                               } else
+                                       outp = videobuf_to_vmalloc(&buf->vb);
+                       }
+
+                       if (buf != NULL) {
+                               if (fbyte & 0x40) {
+                                       buf->top_field = 1;
+                               } else {
+                                       buf->top_field = 0;
+                               }
+                       }
+
+                       dma_q->pos = 0;
+               }
+               if (buf != NULL) {
+                       au0828_copy_video(dev, dma_q, buf, p, outp, len);
+               }
+       }
+       return rc;
+}
+
+static int
+buffer_setup(struct videobuf_queue *vq, unsigned int *count,
+            unsigned int *size)
+{
+       struct au0828_fh *fh = vq->priv_data;
+       *size = (fh->dev->width * fh->dev->height * 16 + 7) >> 3;
+
+       if (0 == *count)
+               *count = AU0828_DEF_BUF;
+
+       if (*count < AU0828_MIN_BUF)
+               *count = AU0828_MIN_BUF;
+       return 0;
+}
+
+/* This is called *without* dev->slock held; please keep it that way */
+static void free_buffer(struct videobuf_queue *vq, struct au0828_buffer *buf)
+{
+       struct au0828_fh     *fh  = vq->priv_data;
+       struct au0828_dev    *dev = fh->dev;
+       unsigned long flags = 0;
+       if (in_interrupt())
+               BUG();
+
+       /* We used to wait for the buffer to finish here, but this didn't work
+          because, as we were keeping the state as VIDEOBUF_QUEUED,
+          videobuf_queue_cancel marked it as finished for us.
+          (Also, it could wedge forever if the hardware was misconfigured.)
+
+          This should be safe; by the time we get here, the buffer isn't
+          queued anymore. If we ever start marking the buffers as
+          VIDEOBUF_ACTIVE, it won't be, though.
+       */
+       spin_lock_irqsave(&dev->slock, flags);
+       if (dev->isoc_ctl.buf == buf)
+               dev->isoc_ctl.buf = NULL;
+       spin_unlock_irqrestore(&dev->slock, flags);
+
+       videobuf_vmalloc_free(&buf->vb);
+       buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int
+buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
+                                               enum v4l2_field field)
+{
+       struct au0828_fh     *fh  = vq->priv_data;
+       struct au0828_buffer *buf = container_of(vb, struct au0828_buffer, vb);
+       struct au0828_dev    *dev = fh->dev;
+       int                  rc = 0, urb_init = 0;
+
+       buf->vb.size = (fh->dev->width * fh->dev->height * 16 + 7) >> 3;
+
+       if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size)
+               return -EINVAL;
+
+       buf->vb.width  = dev->width;
+       buf->vb.height = dev->height;
+       buf->vb.field  = field;
+
+       if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+               rc = videobuf_iolock(vq, &buf->vb, NULL);
+               if (rc < 0) {
+                       printk("videobuf_iolock failed\n");
+                       goto fail;
+               }
+       }
+
+       if (!dev->isoc_ctl.num_bufs)
+               urb_init = 1;
+
+       if (urb_init) {
+               rc = au0828_init_isoc(dev, AU0828_ISO_PACKETS_PER_URB,
+                                     AU0828_MAX_ISO_BUFS, dev->max_pkt_size,
+                                     au0828_isoc_copy);
+               if (rc < 0) {
+                       printk("au0828_init_isoc failed\n");
+                       goto fail;
+               }
+       }
+
+       buf->vb.state = VIDEOBUF_PREPARED;
+       return 0;
+
+fail:
+       free_buffer(vq, buf);
+       return rc;
+}
+
+static void
+buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+       struct au0828_buffer    *buf     = container_of(vb,
+                                                       struct au0828_buffer,
+                                                       vb);
+       struct au0828_fh        *fh      = vq->priv_data;
+       struct au0828_dev       *dev     = fh->dev;
+       struct au0828_dmaqueue  *vidq    = &dev->vidq;
+
+       buf->vb.state = VIDEOBUF_QUEUED;
+       list_add_tail(&buf->vb.queue, &vidq->active);
+}
+
+static void buffer_release(struct videobuf_queue *vq,
+                               struct videobuf_buffer *vb)
+{
+       struct au0828_buffer   *buf  = container_of(vb,
+                                                   struct au0828_buffer,
+                                                   vb);
+
+       free_buffer(vq, buf);
+}
+
+static struct videobuf_queue_ops au0828_video_qops = {
+       .buf_setup      = buffer_setup,
+       .buf_prepare    = buffer_prepare,
+       .buf_queue      = buffer_queue,
+       .buf_release    = buffer_release,
+};
+
+/* ------------------------------------------------------------------
+   V4L2 interface
+   ------------------------------------------------------------------*/
+
+static int au0828_i2s_init(struct au0828_dev *dev)
+{
+       /* Enable i2s mode */
+       au0828_writereg(dev, AU0828_AUDIOCTRL_50C, 0x01);
+       return 0;
+}
+
+/*
+ * Auvitek au0828 analog stream enable
+ * Please set interface0 to AS5 before enable the stream
+ */
+int au0828_analog_stream_enable(struct au0828_dev *d)
+{
+       dprintk(1, "au0828_analog_stream_enable called\n");
+       au0828_writereg(d, AU0828_SENSORCTRL_VBI_103, 0x00);
+       au0828_writereg(d, 0x106, 0x00);
+       /* set x position */
+       au0828_writereg(d, 0x110, 0x00);
+       au0828_writereg(d, 0x111, 0x00);
+       au0828_writereg(d, 0x114, 0xa0);
+       au0828_writereg(d, 0x115, 0x05);
+       /* set y position */
+       au0828_writereg(d, 0x112, 0x02);
+       au0828_writereg(d, 0x113, 0x00);
+       au0828_writereg(d, 0x116, 0xf2);
+       au0828_writereg(d, 0x117, 0x00);
+       au0828_writereg(d, AU0828_SENSORCTRL_100, 0xb3);
+
+       return 0;
+}
+
+int au0828_analog_stream_disable(struct au0828_dev *d)
+{
+       dprintk(1, "au0828_analog_stream_disable called\n");
+       au0828_writereg(d, AU0828_SENSORCTRL_100, 0x0);
+       return 0;
+}
+
+void au0828_analog_stream_reset(struct au0828_dev *dev)
+{
+       dprintk(1, "au0828_analog_stream_reset called\n");
+       au0828_writereg(dev, AU0828_SENSORCTRL_100, 0x0);
+       mdelay(30);
+       au0828_writereg(dev, AU0828_SENSORCTRL_100, 0xb3);
+}
+
+/*
+ * Some operations needs to stop current streaming
+ */
+static int au0828_stream_interrupt(struct au0828_dev *dev)
+{
+       int ret = 0;
+
+       dev->stream_state = STREAM_INTERRUPT;
+       if(dev->dev_state == DEV_DISCONNECTED)
+               return -ENODEV;
+       else if(ret) {
+               dev->dev_state = DEV_MISCONFIGURED;
+               dprintk(1, "%s device is misconfigured!\n", __FUNCTION__);
+               return ret;
+       }
+       return 0;
+}
+
+/*
+ * au0828_release_resources
+ * unregister v4l2 devices
+ */
+void au0828_analog_unregister(struct au0828_dev *dev)
+{
+       dprintk(1, "au0828_release_resources called\n");
+       mutex_lock(&au0828_sysfs_lock);
+
+       list_del(&dev->au0828list);
+       video_unregister_device(dev->vdev);
+       video_unregister_device(dev->vbi_dev);
+
+       mutex_unlock(&au0828_sysfs_lock);
+}
+
+
+/* Usage lock check functions */
+static int res_get(struct au0828_fh *fh)
+{
+       struct au0828_dev *dev = fh->dev;
+       int              rc   = 0;
+
+       /* This instance already has stream_on */
+       if (fh->stream_on)
+               return rc;
+
+       if (dev->stream_on)
+               return -EBUSY;
+
+       dev->stream_on = 1;
+       fh->stream_on  = 1;
+       return rc;
+}
+
+static int res_check(struct au0828_fh *fh)
+{
+       return fh->stream_on;
+}
+
+static void res_free(struct au0828_fh *fh)
+{
+       struct au0828_dev *dev = fh->dev;
+
+       fh->stream_on = 0;
+       dev->stream_on = 0;
+}
+
+static int au0828_v4l2_open(struct file *filp)
+{
+       int minor = video_devdata(filp)->minor;
+       int ret = 0;
+       struct au0828_dev *h, *dev = NULL;
+       struct au0828_fh *fh;
+       int type = 0;
+       struct list_head *list;
+
+       list_for_each(list, &au0828_devlist) {
+               h = list_entry(list, struct au0828_dev, au0828list);
+               if(h->vdev->minor == minor) {
+                       dev = h;
+                       type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               }
+               if(h->vbi_dev->minor == minor) {
+                       dev = h;
+                       type = V4L2_BUF_TYPE_VBI_CAPTURE;
+               }
+       }
+
+       if(NULL == dev)
+               return -ENODEV;
+
+       fh = kzalloc(sizeof(struct au0828_fh), GFP_KERNEL);
+       if(NULL == fh) {
+               dprintk(1, "Failed allocate au0828_fh struct!\n");
+               return -ENOMEM;
+       }
+
+       fh->type = type;
+       fh->dev = dev;
+       filp->private_data = fh;
+
+       if(fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->users == 0) {
+               /* set au0828 interface0 to AS5 here again */
+               ret = usb_set_interface(dev->usbdev, 0, 5);
+               if(ret < 0) {
+                       printk("Au0828 can't set alt setting to 5!\n");
+                       return -EBUSY;
+               }
+               dev->width = NTSC_STD_W;
+               dev->height = NTSC_STD_H;
+               dev->frame_size = dev->width * dev->height * 2;
+               dev->field_size = dev->width * dev->height;
+               dev->bytesperline = dev->width * 2;
+
+               au0828_analog_stream_enable(dev);
+               au0828_analog_stream_reset(dev);
+
+               /* If we were doing ac97 instead of i2s, it would go here...*/
+               au0828_i2s_init(dev);
+
+               dev->stream_state = STREAM_OFF;
+               dev->dev_state |= DEV_INITIALIZED;
+       }
+
+       dev->users++;
+
+       videobuf_queue_vmalloc_init(&fh->vb_vidq, &au0828_video_qops,
+                                   NULL, &dev->slock, fh->type,
+                                   V4L2_FIELD_INTERLACED,
+                                   sizeof(struct au0828_buffer), fh);
+
+       return ret;
+}
+
+static int au0828_v4l2_close(struct file *filp)
+{
+       int ret;
+       struct au0828_fh *fh = filp->private_data;
+       struct au0828_dev *dev = fh->dev;
+
+       mutex_lock(&dev->lock);
+       if (res_check(fh))
+               res_free(fh);
+
+       if(dev->users == 1) {
+               videobuf_stop(&fh->vb_vidq);
+               videobuf_mmap_free(&fh->vb_vidq);
+
+               if(dev->dev_state & DEV_DISCONNECTED) {
+                       au0828_analog_unregister(dev);
+                       mutex_unlock(&dev->lock);
+                       kfree(dev);
+                       return 0;
+               }
+
+               au0828_analog_stream_disable(dev);
+
+               au0828_uninit_isoc(dev);
+
+               /* When close the device, set the usb intf0 into alt0 to free
+                  USB bandwidth */
+               ret = usb_set_interface(dev->usbdev, 0, 0);
+               if(ret < 0)
+                       printk("Au0828 can't set alt setting to 0!\n");
+       }
+
+       kfree(fh);
+       dev->users--;
+       wake_up_interruptible_nr(&dev->open, 1);
+       mutex_unlock(&dev->lock);
+       return 0;
+}
+
+static ssize_t au0828_v4l2_read(struct file *filp, char __user *buf,
+                               size_t count, loff_t *pos)
+{
+       struct au0828_fh *fh = filp->private_data;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       if(fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               mutex_lock(&dev->lock);
+               rc = res_get(fh);
+               mutex_unlock(&dev->lock);
+
+               if (unlikely(rc < 0))
+                       return rc;
+
+               return videobuf_read_stream(&fh->vb_vidq, buf, count, pos, 0,
+                                       filp->f_flags & O_NONBLOCK);
+       }
+       return 0;
+}
+
+static unsigned int au0828_v4l2_poll(struct file *filp, poll_table *wait)
+{
+       struct au0828_fh *fh = filp->private_data;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       mutex_lock(&dev->lock);
+       rc = res_get(fh);
+       mutex_unlock(&dev->lock);
+
+       if (unlikely(rc < 0))
+               return POLLERR;
+
+       if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
+               return POLLERR;
+
+       return videobuf_poll_stream(filp, &fh->vb_vidq, wait);
+}
+
+static int au0828_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+       struct au0828_fh *fh    = filp->private_data;
+       struct au0828_dev *dev   = fh->dev;
+       int              rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       mutex_lock(&dev->lock);
+       rc = res_get(fh);
+       mutex_unlock(&dev->lock);
+
+       if (unlikely(rc < 0))
+               return rc;
+
+       rc = videobuf_mmap_mapper(&fh->vb_vidq, vma);
+
+       dprintk(2, "vma start=0x%08lx, size=%ld, ret=%d\n",
+               (unsigned long)vma->vm_start,
+               (unsigned long)vma->vm_end-(unsigned long)vma->vm_start,
+               rc);
+
+       return rc;
+}
+
+static int au0828_set_format(struct au0828_dev *dev, unsigned int cmd,
+                            struct v4l2_format *format)
+{
+       int ret;
+       int width = format->fmt.pix.width;
+       int height = format->fmt.pix.height;
+       unsigned int maxwidth, maxheight;
+
+       maxwidth = 720;
+       maxheight = 480;
+
+       if(format->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
+               dprintk(1, "VBI format set: to be supported!\n");
+               return 0;
+       }
+       if(format->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+               return 0;
+       }
+       if(format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               return -EINVAL;
+       }
+
+       /* If they are demanding a format other than the one we support,
+          bail out (tvtime asks for UYVY and then retries with YUYV) */
+       if (format->fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) {
+               return -EINVAL;
+       }
+
+       /* format->fmt.pix.width only support 720 and height 480 */
+       if(width != 720)
+               width = 720;
+       if(height != 480)
+               height = 480;
+
+       format->fmt.pix.width = width;
+       format->fmt.pix.height = height;
+       format->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
+       format->fmt.pix.bytesperline = width * 2;
+       format->fmt.pix.sizeimage = width * height * 2;
+       format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+       format->fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+       if(cmd == VIDIOC_TRY_FMT)
+               return 0;
+
+       /* maybe set new image format, driver current only support 720*480 */
+       dev->width = width;
+       dev->height = height;
+       dev->frame_size = width * height * 2;
+       dev->field_size = width * height;
+       dev->bytesperline = width * 2;
+
+       if(dev->stream_state == STREAM_ON) {
+               dprintk(1, "VIDIOC_SET_FMT: interrupting stream!\n");
+               if((ret = au0828_stream_interrupt(dev))) {
+                       dprintk(1, "error interrupting video stream!\n");
+                       return ret;
+               }
+       }
+
+       /* set au0828 interface0 to AS5 here again */
+       ret = usb_set_interface(dev->usbdev, 0, 5);
+       if(ret < 0) {
+               printk("Au0828 can't set alt setting to 5!\n");
+               return -EBUSY;
+       }
+
+       au0828_analog_stream_enable(dev);
+
+       return 0;
+}
+
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+                           struct v4l2_queryctrl *qc)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       au0828_call_i2c_clients(dev, VIDIOC_QUERYCTRL, qc);
+       if (qc->type)
+               return 0;
+       else
+               return -EINVAL;
+}
+
+static int vidioc_querycap(struct file *file, void  *priv,
+                          struct v4l2_capability *cap)
+{
+       struct au0828_fh *fh  = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       memset(cap, 0, sizeof(*cap));
+       strlcpy(cap->driver, "au0828", sizeof(cap->driver));
+       strlcpy(cap->card, au0828_boards[dev->board].name, sizeof(cap->card));
+       strlcpy(cap->bus_info, dev->usbdev->dev.bus_id, sizeof(cap->bus_info));
+
+       cap->version = AU0828_VERSION_CODE;
+
+       /*set the device capabilities */
+       cap->capabilities = V4L2_CAP_VBI_CAPTURE |
+               V4L2_CAP_VIDEO_CAPTURE |
+               V4L2_CAP_AUDIO |
+               V4L2_CAP_READWRITE |
+               V4L2_CAP_STREAMING |
+               V4L2_CAP_TUNER;
+       return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+                                       struct v4l2_fmtdesc *f)
+{
+       if(f->index)
+               return -EINVAL;
+
+       memset(f, 0, sizeof(*f));
+       f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       strcpy(f->description, "Packed YUV2");
+
+       f->flags = 0;
+       f->pixelformat = V4L2_PIX_FMT_UYVY;
+
+       memset(f->reserved, 0, sizeof(f->reserved));
+       return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+                                       struct v4l2_format *f)
+{
+       struct au0828_fh *fh  = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       f->fmt.pix.width = dev->width;
+       f->fmt.pix.height = dev->height;
+       f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
+       f->fmt.pix.bytesperline = dev->bytesperline;
+       f->fmt.pix.sizeimage = dev->frame_size;
+       f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; /* NTSC/PAL */
+       f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+       return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+                                 struct v4l2_format *f)
+{
+       struct au0828_fh *fh  = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       return au0828_set_format(dev, VIDIOC_TRY_FMT, f);
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+                               struct v4l2_format *f)
+{
+       struct au0828_fh *fh  = priv;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       if (videobuf_queue_is_busy(&fh->vb_vidq)) {
+               printk("%s queue busy\n", __func__);
+               rc = -EBUSY;
+               goto out;
+       }
+
+       if (dev->stream_on && !fh->stream_on) {
+               printk("%s device in use by another fh\n", __func__);
+               rc = -EBUSY;
+               goto out;
+       }
+
+       return au0828_set_format(dev, VIDIOC_S_FMT, f);
+out:
+       return rc;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id * norm)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       /* FIXME: when we support something other than NTSC, we are going to
+          have to make the au0828 bridge adjust the size of its capture
+          buffer, which is currently hardcoded at 720x480 */
+
+       au0828_call_i2c_clients(dev, VIDIOC_S_STD, norm);
+       return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+                               struct v4l2_input *input)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       unsigned int tmp;
+
+       static const char *inames[] = {
+               [AU0828_VMUX_COMPOSITE] = "Composite",
+               [AU0828_VMUX_SVIDEO] = "S-Video",
+               [AU0828_VMUX_CABLE] = "Cable TV",
+               [AU0828_VMUX_TELEVISION] = "Television",
+               [AU0828_VMUX_DVB] = "DVB",
+               [AU0828_VMUX_DEBUG] = "tv debug"
+       };
+
+       tmp = input->index;
+
+       if(tmp > AU0828_MAX_INPUT)
+               return -EINVAL;
+       if(AUVI_INPUT(tmp)->type == 0)
+               return -EINVAL;
+
+       memset(input, 0, sizeof(*input));
+       input->index = tmp;
+       strcpy(input->name, inames[AUVI_INPUT(tmp)->type]);
+       if((AUVI_INPUT(tmp)->type == AU0828_VMUX_TELEVISION) ||
+          (AUVI_INPUT(tmp)->type == AU0828_VMUX_CABLE))
+               input->type |= V4L2_INPUT_TYPE_TUNER;
+       else
+               input->type |= V4L2_INPUT_TYPE_CAMERA;
+
+       input->std = dev->vdev->tvnorms;
+
+       return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       *i = dev->ctrl_input;
+       return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int index)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int i;
+       struct v4l2_routing route;
+
+       dprintk(1, "VIDIOC_S_INPUT in function %s, input=%d\n", __FUNCTION__,
+               index);
+       if(index >= AU0828_MAX_INPUT)
+               return -EINVAL;
+       if(AUVI_INPUT(index)->type == 0)
+               return -EINVAL;
+       dev->ctrl_input = index;
+
+       switch(AUVI_INPUT(index)->type) {
+       case AU0828_VMUX_SVIDEO:
+       {
+               dev->input_type = AU0828_VMUX_SVIDEO;
+               break;
+       }
+       case AU0828_VMUX_COMPOSITE:
+       {
+               dev->input_type = AU0828_VMUX_COMPOSITE;
+               break;
+       }
+       case AU0828_VMUX_TELEVISION:
+       {
+               dev->input_type = AU0828_VMUX_TELEVISION;
+               break;
+       }
+       default:
+               ;
+       }
+
+       route.input = AUVI_INPUT(index)->vmux;
+       route.output = 0;
+       au0828_call_i2c_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route);
+
+       for (i = 0; i < AU0828_MAX_INPUT; i++) {
+               int enable = 0;
+               if (AUVI_INPUT(i)->audio_setup == NULL) {
+                       continue;
+               }
+
+               if (i == index)
+                       enable = 1;
+               else
+                       enable = 0;
+               if (enable) {
+                       (AUVI_INPUT(i)->audio_setup)(dev, enable);
+               } else {
+                       /* Make sure we leave it turned on if some
+                          other input is routed to this callback */
+                       if ((AUVI_INPUT(i)->audio_setup) !=
+                           ((AUVI_INPUT(index)->audio_setup))) {
+                               (AUVI_INPUT(i)->audio_setup)(dev, enable);
+                       }
+               }
+       }
+
+       route.input = AUVI_INPUT(index)->amux;
+       au0828_call_i2c_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING,
+                               &route);
+       return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       unsigned int index = a->index;
+
+       if(a->index > 1)
+               return -EINVAL;
+
+       memset(a, 0, sizeof(*a));
+       index = dev->ctrl_ainput;
+       if(index == 0)
+               strcpy(a->name, "Television");
+       else
+               strcpy(a->name, "Line in");
+
+       a->capability = V4L2_AUDCAP_STEREO;
+       a->index = index;
+       return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       if(a->index != dev->ctrl_ainput)
+               return -EINVAL;
+       return 0;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+                        struct v4l2_control *ctrl)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       au0828_call_i2c_clients(dev, VIDIOC_G_CTRL, ctrl);
+       return 0;
+
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+                               struct v4l2_control *ctrl)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       au0828_call_i2c_clients(dev, VIDIOC_S_CTRL, ctrl);
+       return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       if(t->index != 0)
+               return -EINVAL;
+
+       memset(t, 0, sizeof(*t));
+       strcpy(t->name, "Auvitek tuner");
+
+       au0828_call_i2c_clients(dev, VIDIOC_G_TUNER, t);
+       return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+                               struct v4l2_tuner *t)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       if(t->index != 0)
+               return -EINVAL;
+
+       t->type = V4L2_TUNER_ANALOG_TV;
+       au0828_call_i2c_clients(dev, VIDIOC_S_TUNER, t);
+       dprintk(1, "VIDIOC_S_TUNER: signal = %x, afc = %x\n", t->signal,
+               t->afc);
+       return 0;
+
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+                               struct v4l2_frequency *freq)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       memset(freq, 0, sizeof(*freq));
+       freq->type = V4L2_TUNER_ANALOG_TV;
+       freq->frequency = dev->ctrl_freq;
+       return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+                               struct v4l2_frequency *freq)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       if(freq->tuner != 0)
+               return -EINVAL;
+       if(freq->type != V4L2_TUNER_ANALOG_TV)
+               return -EINVAL;
+
+       dev->ctrl_freq = freq->frequency;
+
+       au0828_call_i2c_clients(dev, VIDIOC_S_FREQUENCY, freq);
+
+       au0828_analog_stream_reset(dev);
+
+       return 0;
+}
+
+static int vidioc_g_chip_ident(struct file *file, void *priv,
+              struct v4l2_dbg_chip_ident *chip)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       chip->ident = V4L2_IDENT_NONE;
+       chip->revision = 0;
+
+       au0828_call_i2c_clients(dev, VIDIOC_DBG_G_CHIP_IDENT, chip);
+       return 0;
+}
+
+static int vidioc_cropcap(struct file *file, void *priv,
+                         struct v4l2_cropcap *cc)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       if(cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return -EINVAL;
+
+       cc->bounds.left = 0;
+       cc->bounds.top = 0;
+       cc->bounds.width = dev->width;
+       cc->bounds.height = dev->height;
+
+       cc->defrect = cc->bounds;
+
+       cc->pixelaspect.numerator = 54;
+       cc->pixelaspect.denominator = 59;
+
+       return 0;
+}
+
+static int vidioc_streamon(struct file *file, void *priv,
+                          enum v4l2_buf_type type)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int b = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               au0828_analog_stream_enable(dev);
+               au0828_call_i2c_clients(dev, VIDIOC_STREAMON, &b);
+       }
+
+       mutex_lock(&dev->lock);
+       rc = res_get(fh);
+
+       if (likely(rc >= 0))
+               rc = videobuf_streamon(&fh->vb_vidq);
+       mutex_unlock(&dev->lock);
+
+       return rc;
+}
+
+static int vidioc_streamoff(struct file *file, void *priv,
+                           enum v4l2_buf_type type)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int b = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       int i;
+       int ret;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return -EINVAL;
+       if (type != fh->type)
+               return -EINVAL;
+
+       if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               au0828_call_i2c_clients(dev, VIDIOC_STREAMOFF, &b);
+               if((ret = au0828_stream_interrupt(dev)) != 0)
+                       return ret;
+       }
+
+       for (i = 0; i < AU0828_MAX_INPUT; i++) {
+               if (AUVI_INPUT(i)->audio_setup == NULL) {
+                       continue;
+               }
+               (AUVI_INPUT(i)->audio_setup)(dev, 0);
+       }
+
+       mutex_lock(&dev->lock);
+       videobuf_streamoff(&fh->vb_vidq);
+       res_free(fh);
+       mutex_unlock(&dev->lock);
+
+       return 0;
+}
+
+static int vidioc_g_register(struct file *file, void *priv,
+                            struct v4l2_dbg_register *reg)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       switch (reg->match.type) {
+       case V4L2_CHIP_MATCH_I2C_DRIVER:
+               au0828_call_i2c_clients(dev, VIDIOC_DBG_G_REGISTER, reg);
+               return 0;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+                            struct v4l2_dbg_register *reg)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+
+       switch (reg->match.type) {
+       case V4L2_CHIP_MATCH_I2C_DRIVER:
+               au0828_call_i2c_clients(dev, VIDIOC_DBG_S_REGISTER, reg);
+               return 0;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int vidioc_reqbufs(struct file *file, void *priv,
+                         struct v4l2_requestbuffers *rb)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       return videobuf_reqbufs(&fh->vb_vidq, rb);
+}
+
+static int vidioc_querybuf(struct file *file, void *priv,
+                          struct v4l2_buffer *b)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       return videobuf_querybuf(&fh->vb_vidq, b);
+}
+
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       return videobuf_qbuf(&fh->vb_vidq, b);
+}
+
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+       struct au0828_fh *fh = priv;
+       struct au0828_dev *dev = fh->dev;
+       int rc;
+
+       rc = check_dev(dev);
+       if (rc < 0)
+               return rc;
+
+       /* Workaround for a bug in the au0828 hardware design that sometimes
+          results in the colorspace being inverted */
+       if (dev->greenscreen_detected == 1) {
+               dprintk(1, "Detected green frame.  Resetting stream...\n");
+               au0828_analog_stream_reset(dev);
+               dev->greenscreen_detected = 0;
+       }
+
+       return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags & O_NONBLOCK);
+}
+
+#ifdef CONFIG_VIDEO_V4L1_COMPAT
+static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
+{
+       struct au0828_fh *fh = priv;
+
+       return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8);
+}
+#endif
+
+static struct v4l2_file_operations au0828_v4l_fops = {
+       .owner      = THIS_MODULE,
+       .open       = au0828_v4l2_open,
+       .release    = au0828_v4l2_close,
+       .read       = au0828_v4l2_read,
+       .poll       = au0828_v4l2_poll,
+       .mmap       = au0828_v4l2_mmap,
+       .ioctl      = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+       .vidioc_querycap            = vidioc_querycap,
+       .vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid_cap,
+       .vidioc_g_fmt_vid_cap       = vidioc_g_fmt_vid_cap,
+       .vidioc_try_fmt_vid_cap     = vidioc_try_fmt_vid_cap,
+       .vidioc_s_fmt_vid_cap       = vidioc_s_fmt_vid_cap,
+       .vidioc_g_audio             = vidioc_g_audio,
+       .vidioc_s_audio             = vidioc_s_audio,
+       .vidioc_cropcap             = vidioc_cropcap,
+#ifdef AAA
+       .vidioc_g_fmt_sliced_vbi_cap   = vidioc_g_fmt_sliced_vbi_cap,
+       .vidioc_try_fmt_sliced_vbi_cap = vidioc_try_set_sliced_vbi_cap,
+       .vidioc_s_fmt_sliced_vbi_cap   = vidioc_try_set_sliced_vbi_cap,
+#endif
+       .vidioc_reqbufs             = vidioc_reqbufs,
+       .vidioc_querybuf            = vidioc_querybuf,
+       .vidioc_qbuf                = vidioc_qbuf,
+       .vidioc_dqbuf               = vidioc_dqbuf,
+       .vidioc_s_std               = vidioc_s_std,
+       .vidioc_enum_input          = vidioc_enum_input,
+       .vidioc_g_input             = vidioc_g_input,
+       .vidioc_s_input             = vidioc_s_input,
+       .vidioc_queryctrl           = vidioc_queryctrl,
+       .vidioc_g_ctrl              = vidioc_g_ctrl,
+       .vidioc_s_ctrl              = vidioc_s_ctrl,
+       .vidioc_streamon            = vidioc_streamon,
+       .vidioc_streamoff           = vidioc_streamoff,
+       .vidioc_g_tuner             = vidioc_g_tuner,
+       .vidioc_s_tuner             = vidioc_s_tuner,
+       .vidioc_g_frequency         = vidioc_g_frequency,
+       .vidioc_s_frequency         = vidioc_s_frequency,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+       .vidioc_g_register          = vidioc_g_register,
+       .vidioc_s_register          = vidioc_s_register,
+       .vidioc_g_chip_ident        = vidioc_g_chip_ident,
+#endif
+#ifdef CONFIG_VIDEO_V4L1_COMPAT
+       .vidiocgmbuf                = vidiocgmbuf,
+#endif
+};
+
+static const struct video_device au0828_video_template = {
+       .fops                       = &au0828_v4l_fops,
+       .release                    = video_device_release,
+       .ioctl_ops                  = &video_ioctl_ops,
+       .minor                      = -1,
+       .tvnorms                    = V4L2_STD_NTSC,
+       .current_norm               = V4L2_STD_NTSC,
+};
+
+/**************************************************************************/
+
+int au0828_analog_register(struct au0828_dev *dev)
+{
+       int retval = -ENOMEM;
+
+       dprintk(1, "au0828_analog_register called!\n");
+
+       /* Load the analog demodulator driver (note this would need to be
+          abstracted out if we ever need to support a different demod) */
+       request_module("au8522");
+
+       /* Load the tuner module, which results in i2c enumeration and
+          attachment of whatever tuner is on the bus */
+       request_module("tuner");
+
+       init_waitqueue_head(&dev->open);
+       spin_lock_init(&dev->slock);
+       mutex_init(&dev->lock);
+
+       INIT_LIST_HEAD(&dev->vidq.active);
+       INIT_LIST_HEAD(&dev->vidq.queued);
+
+       dev->width = NTSC_STD_W;
+       dev->height = NTSC_STD_H;
+       dev->field_size = dev->width * dev->height;
+       dev->frame_size = dev->field_size << 1;
+       dev->bytesperline = dev->width << 1;
+       dev->ctrl_ainput = 0;
+
+       /* allocate and fill v4l2 video struct */
+       dev->vdev = video_device_alloc();
+       if(NULL == dev->vdev) {
+               dprintk(1, "Can't allocate video_device.\n");
+               return -ENOMEM;
+       }
+
+       dev->vbi_dev = video_device_alloc();
+       if(NULL == dev->vbi_dev) {
+               dprintk(1, "Can't allocate vbi_device.\n");
+               kfree(dev->vdev);
+               return -ENOMEM;
+       }
+
+       /* Fill the video capture device struct */
+       *dev->vdev = au0828_video_template;
+       dev->vdev->vfl_type = VID_TYPE_CAPTURE | VID_TYPE_TUNER;
+       dev->vdev->parent = &dev->usbdev->dev;
+       strcpy(dev->vdev->name, "au0828a video");
+
+       /* Setup the VBI device */
+       *dev->vbi_dev = au0828_video_template;
+       dev->vbi_dev->vfl_type = VFL_TYPE_VBI;
+       dev->vbi_dev->parent = &dev->usbdev->dev;
+       strcpy(dev->vbi_dev->name, "au0828a vbi");
+
+       list_add_tail(&dev->au0828list, &au0828_devlist);
+
+       /* Register the v4l2 device */
+       if((retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER, -1)) != 0) {
+               dprintk(1, "unable to register video device (error = %d).\n", retval);
+               list_del(&dev->au0828list);
+               video_device_release(dev->vdev);
+               return -ENODEV;
+       }
+
+       /* Register the vbi device */
+       if((retval = video_register_device(dev->vbi_dev, VFL_TYPE_VBI, -1)) != 0) {
+               dprintk(1, "unable to register vbi device (error = %d).\n", retval);
+               list_del(&dev->au0828list);
+               video_device_release(dev->vbi_dev);
+               video_device_release(dev->vdev);
+               return -ENODEV;
+       }
+
+       dprintk(1, "%s completed!\n", __FUNCTION__);
+
+       return 0;
+}
+
index 9d6a116..3b8e3e9 100644 (file)
 #include <linux/i2c-algo-bit.h>
 #include <media/tveeprom.h>
 
+/* Analog */
+#include <linux/videodev2.h>
+#include <media/videobuf-vmalloc.h>
+
 /* DVB */
 #include "demux.h"
 #include "dmxdev.h"
 #define URB_COUNT   16
 #define URB_BUFSIZE (0xe522)
 
+/* Analog constants */
+#define NTSC_STD_W      720
+#define NTSC_STD_H      480
+
+#define AU0828_INTERLACED_DEFAULT       1
+#define V4L2_CID_PRIVATE_SHARPNESS  (V4L2_CID_PRIVATE_BASE + 0)
+
+/* Defination for AU0828 USB transfer */
+#define AU0828_MAX_ISO_BUFS    12  /* maybe resize this value in the future */
+#define AU0828_ISO_PACKETS_PER_URB      10
+#define AU0828_ISO_MAX_FRAME_SIZE       (3 * 1024)
+#define AU0828_ISO_BUFFER_SIZE          (AU0828_ISO_PACKETS_PER_URB * AU0828_ISO_MAX_FRAME_SIZE)
+
+#define AU0828_MIN_BUF 4
+#define AU0828_DEF_BUF 8
+
+#define AU0828_MAX_IMAGES       10
+#define AU0828_FRAME_SIZE       (1028 * 1024 * 4)
+#define AU0828_URB_TIMEOUT      msecs_to_jiffies(AU0828_MAX_ISO_BUFS * AU0828_ISO_PACKETS_PER_URB)
+
+#define AU0828_MAX_INPUT        4
+
+enum au0828_itype {
+       AU0828_VMUX_COMPOSITE = 1,
+       AU0828_VMUX_SVIDEO,
+       AU0828_VMUX_CABLE,
+       AU0828_VMUX_TELEVISION,
+       AU0828_VMUX_DVB,
+       AU0828_VMUX_DEBUG
+};
+
+struct au0828_input {
+       enum au0828_itype type;
+       unsigned int vmux;
+       unsigned int amux;
+       void (*audio_setup) (void *priv, int enable);
+};
+
 struct au0828_board {
        char *name;
+       struct au0828_input input[AU0828_MAX_INPUT];
+
 };
 
 struct au0828_dvb {
@@ -55,6 +99,83 @@ struct au0828_dvb {
        int feeding;
 };
 
+enum au0828_stream_state {
+       STREAM_OFF,
+       STREAM_INTERRUPT,
+       STREAM_ON
+};
+
+#define AUVI_INPUT(nr) (&au0828_boards[dev->board].input[nr])
+
+/* device state */
+enum au0828_dev_state {
+       DEV_INITIALIZED = 0x01,
+       DEV_DISCONNECTED = 0x02,
+       DEV_MISCONFIGURED = 0x04
+};
+
+struct au0828_fh {
+       struct au0828_dev *dev;
+       unsigned int  stream_on:1;      /* Locks streams */
+       struct videobuf_queue        vb_vidq;
+       enum v4l2_buf_type           type;
+};
+
+struct au0828_usb_isoc_ctl {
+               /* max packet size of isoc transaction */
+       int                             max_pkt_size;
+
+               /* number of allocated urbs */
+       int                             num_bufs;
+
+               /* urb for isoc transfers */
+       struct urb                      **urb;
+
+               /* transfer buffers for isoc transfer */
+       char                            **transfer_buffer;
+
+               /* Last buffer command and region */
+       u8                              cmd;
+       int                             pos, size, pktsize;
+
+               /* Last field: ODD or EVEN? */
+       int                             field;
+
+               /* Stores incomplete commands */
+       u32                             tmp_buf;
+       int                             tmp_buf_len;
+
+               /* Stores already requested buffers */
+       struct au0828_buffer            *buf;
+
+               /* Stores the number of received fields */
+       int                             nfields;
+
+               /* isoc urb callback */
+       int (*isoc_copy) (struct au0828_dev *dev, struct urb *urb);
+
+};
+
+/* buffer for one video frame */
+struct au0828_buffer {
+       /* common v4l buffer stuff -- must be first */
+       struct videobuf_buffer vb;
+
+       struct list_head frame;
+       int top_field;
+       int receiving;
+};
+
+struct au0828_dmaqueue {
+       struct list_head       active;
+       struct list_head       queued;
+
+       wait_queue_head_t          wq;
+
+       /* Counters to control buffer fill */
+       int                        pos;
+};
+
 struct au0828_dev {
        struct mutex mutex;
        struct usb_device       *usbdev;
@@ -70,16 +191,49 @@ struct au0828_dev {
        /* Digital */
        struct au0828_dvb               dvb;
 
+       /* Analog */
+       struct list_head au0828list;
+       int users;
+       unsigned int stream_on:1;       /* Locks streams */
+       struct video_device *vdev;
+       struct video_device *vbi_dev;
+       int width;
+       int height;
+       u32 field_size;
+       u32 frame_size;
+       u32 bytesperline;
+       int type;
+       u8 ctrl_ainput;
+       __u8 isoc_in_endpointaddr;
+       u8 isoc_init_ok;
+       int greenscreen_detected;
+       unsigned int frame_count;
+       int ctrl_freq;
+       int input_type;
+       unsigned int ctrl_input;
+       enum au0828_dev_state dev_state;
+       enum au0828_stream_state stream_state;
+       wait_queue_head_t open;
+
+       struct mutex lock;
+
+       /* Isoc control struct */
+       struct au0828_dmaqueue vidq;
+       struct au0828_usb_isoc_ctl isoc_ctl;
+       spinlock_t slock;
+
+       /* usb transfer */
+       int alt;                /* alternate */
+       int max_pkt_size;       /* max packet size of isoc transaction */
+       int num_alt;            /* Number of alternative settings */
+       unsigned int *alt_max_pkt_size; /* array of wMaxPacketSize */
+       struct urb *urb[AU0828_MAX_ISO_BUFS];   /* urb for isoc transfers */
+       char *transfer_buffer[AU0828_MAX_ISO_BUFS];/* transfer buffers for isoc
+                                                  transfer */
+
        /* USB / URB Related */
        int             urb_streaming;
        struct urb      *urbs[URB_COUNT];
-
-};
-
-struct au0828_buff {
-       struct au0828_dev       *dev;
-       struct urb              *purb;
-       struct list_head        buff_list;
 };
 
 /* ----------------------------------------------------------- */
@@ -115,6 +269,12 @@ extern void au0828_call_i2c_clients(struct au0828_dev *dev,
        unsigned int cmd, void *arg);
 
 /* ----------------------------------------------------------- */
+/* au0828-video.c */
+int au0828_analog_register(struct au0828_dev *dev);
+int au0828_analog_stream_disable(struct au0828_dev *d);
+void au0828_analog_unregister(struct au0828_dev *dev);
+
+/* ----------------------------------------------------------- */
 /* au0828-dvb.c */
 extern int au0828_dvb_register(struct au0828_dev *dev);
 extern void au0828_dvb_unregister(struct au0828_dev *dev);