--- /dev/null
+/*
+   em28xx-vbi.c - VBI driver for em28xx
+
+   Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com>
+
+   This work was sponsored by EyeMagnet Limited.
+
+   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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+#include "em28xx.h"
+
+static unsigned int vbibufs = 5;
+module_param(vbibufs,int,0644);
+MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32");
+
+static unsigned int vbi_debug;
+module_param(vbi_debug,int,0644);
+MODULE_PARM_DESC(vbi_debug,"enable debug messages [vbi]");
+
+#define dprintk(level,fmt, arg...)     if (vbi_debug >= level) \
+       printk(KERN_DEBUG "%s: " fmt, dev->core->name , ## arg)
+
+/* ------------------------------------------------------------------ */
+
+static void
+free_buffer(struct videobuf_queue *vq, struct em28xx_buffer *buf)
+{
+       struct em28xx_fh     *fh  = vq->priv_data;
+       struct em28xx        *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.vbi_buf == buf)
+               dev->isoc_ctl.vbi_buf = NULL;
+       spin_unlock_irqrestore(&dev->slock, flags);
+
+       videobuf_vmalloc_free(&buf->vb);
+       buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int
+vbi_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size)
+{
+       *size = 720 * 12 * 2;
+       if (0 == *count)
+               *count = vbibufs;
+       if (*count < 2)
+               *count = 2;
+       if (*count > 32)
+               *count = 32;
+       return 0;
+}
+
+static int
+vbi_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb,
+           enum v4l2_field field)
+{
+       struct em28xx_fh     *fh  = q->priv_data;
+       struct em28xx_buffer *buf = container_of(vb, struct em28xx_buffer, vb);
+       int                  rc = 0;
+       unsigned int size;
+
+       size = 720 * 12 * 2;
+
+       buf->vb.size = size;
+
+       if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size)
+               return -EINVAL;
+
+       buf->vb.width  = 720;
+       buf->vb.height = 12;
+       buf->vb.field  = field;
+
+       if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+               rc = videobuf_iolock(q, &buf->vb, NULL);
+               if (rc < 0)
+                       goto fail;
+       }
+
+       buf->vb.state = VIDEOBUF_PREPARED;
+       return 0;
+
+fail:
+       free_buffer(q, buf);
+       return rc;
+}
+
+static void
+vbi_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+       struct em28xx_buffer    *buf     = container_of(vb,
+                                                       struct em28xx_buffer,
+                                                       vb);
+       struct em28xx_fh        *fh      = vq->priv_data;
+       struct em28xx           *dev     = fh->dev;
+       struct em28xx_dmaqueue  *vbiq    = &dev->vbiq;
+
+       buf->vb.state = VIDEOBUF_QUEUED;
+       list_add_tail(&buf->vb.queue, &vbiq->active);
+}
+
+static void vbi_release(struct videobuf_queue *q, struct videobuf_buffer *vb)
+{
+       struct em28xx_buffer *buf = container_of(vb, struct em28xx_buffer, vb);
+       free_buffer(q, buf);
+}
+
+struct videobuf_queue_ops em28xx_vbi_qops = {
+       .buf_setup    = vbi_setup,
+       .buf_prepare  = vbi_prepare,
+       .buf_queue    = vbi_queue,
+       .buf_release  = vbi_release,
+};
+
+/* ------------------------------------------------------------------ */
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
 
        buf->vb.field_count++;
        do_gettimeofday(&buf->vb.ts);
 
-       dev->isoc_ctl.buf = NULL;
+       dev->isoc_ctl.vid_buf = NULL;
+
+       list_del(&buf->vb.queue);
+       wake_up(&buf->vb.done);
+}
+
+static inline void vbi_buffer_filled(struct em28xx *dev,
+                                    struct em28xx_dmaqueue *dma_q,
+                                    struct em28xx_buffer *buf)
+{
+       /* Advice that buffer was filled */
+       em28xx_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.vbi_buf = NULL;
 
        list_del(&buf->vb.queue);
        wake_up(&buf->vb.done);
        dma_q->pos += len;
 }
 
+static void em28xx_copy_vbi(struct em28xx *dev,
+                             struct em28xx_dmaqueue  *dma_q,
+                             struct em28xx_buffer *buf,
+                             unsigned char *p,
+                             unsigned char *outp, unsigned long len)
+{
+       void *startwrite, *startread;
+       int  offset;
+       int bytesperline = 720;
+
+       if (dev == NULL) {
+               printk("dev is null\n");
+               return;
+       }
+
+       if (dma_q == NULL) {
+               printk("dma_q is null\n");
+               return;
+       }
+       if (buf == NULL) {
+               return;
+       }
+       if (p == NULL) {
+               printk("p is null\n");
+               return;
+       }
+       if (outp == NULL) {
+               printk("outp is null\n");
+               return;
+       }
+
+       if (dma_q->pos + len > buf->vb.size)
+               len = buf->vb.size - dma_q->pos;
+
+       if ((p[0] == 0x33 && p[1] == 0x95) ||
+           (p[0] == 0x88 && p[1] == 0x88)) {
+               /* Header field, advance past it */
+               p += 4;
+       } else {
+               len += 4;
+       }
+
+       startread = p;
+
+       startwrite = outp + dma_q->pos;
+       offset = dma_q->pos;
+
+       /* Make sure the bottom field populates the second half of the frame */
+       if (buf->top_field == 0) {
+               startwrite += bytesperline * 0x0c;
+               offset += bytesperline * 0x0c;
+       }
+
+       memcpy(startwrite, startread, len);
+       dma_q->pos += len;
+}
+
 static inline void print_err_status(struct em28xx *dev,
                                     int packet, int status)
 {
 
        if (list_empty(&dma_q->active)) {
                em28xx_isocdbg("No active queue to serve\n");
-               dev->isoc_ctl.buf = NULL;
+               dev->isoc_ctl.vid_buf = NULL;
                *buf = NULL;
                return;
        }
        outp = videobuf_to_vmalloc(&(*buf)->vb);
        memset(outp, 0, (*buf)->vb.size);
 
-       dev->isoc_ctl.buf = *buf;
+       dev->isoc_ctl.vid_buf = *buf;
+
+       return;
+}
+
+/*
+ * video-buf generic routine to get the next available VBI buffer
+ */
+static inline void vbi_get_next_buf(struct em28xx_dmaqueue *dma_q,
+                                   struct em28xx_buffer **buf)
+{
+       struct em28xx *dev = container_of(dma_q, struct em28xx, vbiq);
+       char *outp;
+
+       if (list_empty(&dma_q->active)) {
+               em28xx_isocdbg("No active queue to serve\n");
+               dev->isoc_ctl.vbi_buf = NULL;
+               *buf = NULL;
+               return;
+       }
+
+       /* Get the next buffer */
+       *buf = list_entry(dma_q->active.next, struct em28xx_buffer, vb.queue);
+       /* Cleans up buffer - Usefull for testing for frame/URB loss */
+       outp = videobuf_to_vmalloc(&(*buf)->vb);
+       memset(outp, 0x00, (*buf)->vb.size);
+
+       dev->isoc_ctl.vbi_buf = *buf;
 
        return;
 }
                        return 0;
        }
 
-       buf = dev->isoc_ctl.buf;
+       buf = dev->isoc_ctl.vid_buf;
        if (buf != NULL)
                outp = videobuf_to_vmalloc(&buf->vb);
 
 {
        struct em28xx_buffer    *buf, *vbi_buf;
        struct em28xx_dmaqueue  *dma_q = &dev->vidq;
+       struct em28xx_dmaqueue  *vbi_dma_q = &dev->vbiq;
        unsigned char *outp = NULL;
        unsigned char *vbioutp = NULL;
        int i, len = 0, rc = 1;
                        return 0;
        }
 
-       buf = dev->isoc_ctl.buf;
+       buf = dev->isoc_ctl.vid_buf;
        if (buf != NULL)
                outp = videobuf_to_vmalloc(&buf->vb);
+
+       vbi_buf = dev->isoc_ctl.vbi_buf;
+       if (vbi_buf != NULL)
+               vbioutp = videobuf_to_vmalloc(&vbi_buf->vb);
+
        for (i = 0; i < urb->number_of_packets; i++) {
                int status = urb->iso_frame_desc[i].status;
 
                                printk("djh c should never happen\n");
                        } else if ((dev->vbi_read + len) < vbi_size) {
                                /* This entire frame is VBI data */
+                               if (dev->vbi_read == 0 &&
+                                   (!(dev->cur_field & 1))) {
+                                       /* Brand new frame */
+                                       if (vbi_buf != NULL)
+                                               vbi_buffer_filled(dev,
+                                                                 vbi_dma_q,
+                                                                 vbi_buf);
+                                       vbi_get_next_buf(vbi_dma_q, &vbi_buf);
+                                       if (vbi_buf == NULL)
+                                               vbioutp = NULL;
+                                       else {
+                                               vbioutp = videobuf_to_vmalloc(&vbi_buf->vb);
+                                       }
+                               }
+
+                               if (dev->vbi_read == 0) {
+                                       vbi_dma_q->pos = 0;
+                                       if (vbi_buf != NULL) {
+                                               if (dev->cur_field & 1)
+                                                       vbi_buf->top_field = 0;
+                                               else
+                                                       vbi_buf->top_field = 1;
+                                       }
+                               }
+
                                dev->vbi_read += len;
+                               em28xx_copy_vbi(dev, vbi_dma_q, vbi_buf, p,
+                                               vbioutp, len);
                        } else {
                                /* Some of this frame is VBI data and some is
                                   video data */
                                int vbi_data_len = vbi_size - dev->vbi_read;
                                dev->vbi_read += vbi_data_len;
+                               em28xx_copy_vbi(dev, vbi_dma_q, vbi_buf, p,
+                                               vbioutp, vbi_data_len);
                                dev->capture_type = 1;
                                p += vbi_data_len;
                                len -= vbi_data_len;
           VIDEOBUF_ACTIVE, it won't be, though.
        */
        spin_lock_irqsave(&dev->slock, flags);
-       if (dev->isoc_ctl.buf == buf)
-               dev->isoc_ctl.buf = NULL;
+       if (dev->isoc_ctl.vid_buf == buf)
+               dev->isoc_ctl.vid_buf = NULL;
        spin_unlock_irqrestore(&dev->slock, flags);
 
        videobuf_vmalloc_free(&buf->vb);
        mutex_lock(&dev->lock);
        rc = res_get(fh);
 
-       if (likely(rc >= 0))
-               rc = videobuf_streamon(&fh->vb_vidq);
+       if (likely(rc >= 0)) {
+               if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+                       rc = videobuf_streamon(&fh->vb_vidq);
+               else if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
+                       rc = videobuf_streamon(&fh->vb_vbiq);
+       }
 
        mutex_unlock(&dev->lock);
 
        if (rc < 0)
                return rc;
 
-       if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+       if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+           fh->type != V4L2_BUF_TYPE_VBI_CAPTURE)
                return -EINVAL;
        if (type != fh->type)
                return -EINVAL;
 
        mutex_lock(&dev->lock);
 
-       videobuf_streamoff(&fh->vb_vidq);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               videobuf_streamoff(&fh->vb_vidq);
+       else if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
+               videobuf_streamoff(&fh->vb_vbiq);
+
        res_free(fh);
 
        mutex_unlock(&dev->lock);
        cap->version = EM28XX_VERSION_CODE;
 
        cap->capabilities =
+                       V4L2_CAP_VBI_CAPTURE |
                        V4L2_CAP_SLICED_VBI_CAPTURE |
                        V4L2_CAP_VIDEO_CAPTURE |
                        V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
        return 0;
 }
 
+/* RAW VBI ioctls */
+
+static int vidioc_g_fmt_vbi_cap(struct file *file, void *priv,
+                               struct v4l2_format *format)
+{
+       format->fmt.vbi.samples_per_line = 720;
+       format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+       format->fmt.vbi.offset = 0;
+       format->fmt.vbi.flags = 0;
+
+       /* Varies by video standard (NTSC, PAL, etc.) */
+       /* FIXME: hard-coded for NTSC support */
+       format->fmt.vbi.sampling_rate = 6750000 * 4 / 2; /* FIXME: ??? */
+       format->fmt.vbi.count[0] = 12;
+       format->fmt.vbi.count[1] = 12;
+       format->fmt.vbi.start[0] = 10;
+       format->fmt.vbi.start[1] = 273;
+
+       return 0;
+}
+
+static int vidioc_s_fmt_vbi_cap(struct file *file, void *priv,
+                               struct v4l2_format *format)
+{
+       format->fmt.vbi.samples_per_line = 720;
+       format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+       format->fmt.vbi.offset = 0;
+       format->fmt.vbi.flags = 0;
+
+       /* Varies by video standard (NTSC, PAL, etc.) */
+       /* FIXME: hard-coded for NTSC support */
+       format->fmt.vbi.sampling_rate = 6750000 * 4 / 2; /* FIXME: ??? */
+       format->fmt.vbi.count[0] = 12;
+       format->fmt.vbi.count[1] = 12;
+       format->fmt.vbi.start[0] = 10;
+       format->fmt.vbi.start[1] = 273;
+
+       return 0;
+}
 
 static int vidioc_reqbufs(struct file *file, void *priv,
                          struct v4l2_requestbuffers *rb)
        if (rc < 0)
                return rc;
 
-       return videobuf_reqbufs(&fh->vb_vidq, rb);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return videobuf_reqbufs(&fh->vb_vidq, rb);
+       else
+               return videobuf_reqbufs(&fh->vb_vbiq, rb);
 }
 
 static int vidioc_querybuf(struct file *file, void *priv,
        if (rc < 0)
                return rc;
 
-       return videobuf_querybuf(&fh->vb_vidq, b);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return videobuf_querybuf(&fh->vb_vidq, b);
+       else {
+               /* FIXME: I'm not sure yet whether this is a bug in zvbi or
+                  the videobuf framework, but we probably shouldn't be
+                  returning a buffer larger than that which was asked for.
+                  At a minimum, it causes a crash in zvbi since it does
+                  a memcpy based on the source buffer length */
+               int result = videobuf_querybuf(&fh->vb_vbiq, b);
+               b->length = 17280;
+               return result;
+       }
 }
 
 static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
        if (rc < 0)
                return rc;
 
-       return videobuf_qbuf(&fh->vb_vidq, b);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return videobuf_qbuf(&fh->vb_vidq, b);
+       else {
+               return videobuf_qbuf(&fh->vb_vbiq, b);
+       }
 }
 
 static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
        if (rc < 0)
                return rc;
 
-       return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags & O_NONBLOCK);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags &
+                                     O_NONBLOCK);
+       else
+               return videobuf_dqbuf(&fh->vb_vbiq, b, file->f_flags &
+                                     O_NONBLOCK);
 }
 
 #ifdef CONFIG_VIDEO_V4L1_COMPAT
 {
        struct em28xx_fh  *fh = priv;
 
-       return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8);
+       else
+               return videobuf_cgmbuf(&fh->vb_vbiq, mbuf, 8);
 }
 #endif
 
        else
                field = V4L2_FIELD_INTERLACED;
 
-       videobuf_queue_vmalloc_init(&fh->vb_vidq, &em28xx_video_qops,
-                       NULL, &dev->slock, fh->type, field,
-                       sizeof(struct em28xx_buffer), fh);
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               videobuf_queue_vmalloc_init(&fh->vb_vidq, &em28xx_video_qops,
+                                           NULL, &dev->slock, fh->type, field,
+                                           sizeof(struct em28xx_buffer), fh);
+
+       if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
+               videobuf_queue_vmalloc_init(&fh->vb_vbiq, &em28xx_vbi_qops,
+                                           NULL, &dev->slock,
+                                           V4L2_BUF_TYPE_VBI_CAPTURE,
+                                           V4L2_FIELD_SEQ_TB,
+                                           sizeof(struct em28xx_buffer), fh);
 
        mutex_unlock(&dev->lock);
 
        if (res_check(fh))
                res_free(fh);
 
-       if (dev->users == 1) {
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->users == 1) {
                videobuf_stop(&fh->vb_vidq);
                videobuf_mmap_free(&fh->vb_vidq);
 
                                        "0 (error=%i)\n", errCode);
                }
        }
+
+       if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+               videobuf_stop(&fh->vb_vbiq);
+               videobuf_mmap_free(&fh->vb_vbiq);
+       }
+
        kfree(fh);
        dev->users--;
        wake_up_interruptible_nr(&dev->open, 1);
                return videobuf_read_stream(&fh->vb_vidq, buf, count, pos, 0,
                                        filp->f_flags & O_NONBLOCK);
        }
+
+
+       if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+               mutex_lock(&dev->lock);
+               rc = res_get(fh);
+               mutex_unlock(&dev->lock);
+
+               return videobuf_read_stream(&fh->vb_vbiq, buf, count, pos, 0,
+                                       filp->f_flags & O_NONBLOCK);
+       }
+
        return 0;
 }
 
        if (unlikely(rc < 0))
                return POLLERR;
 
-       if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
+       if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return videobuf_poll_stream(filp, &fh->vb_vidq, wait);
+       else if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
+               return videobuf_poll_stream(filp, &fh->vb_vbiq, wait);
+       else
                return POLLERR;
-
-       return videobuf_poll_stream(filp, &fh->vb_vidq, wait);
 }
 
 /*
        .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_fmt_vbi_cap       = vidioc_g_fmt_vbi_cap,
+       .vidioc_s_fmt_vbi_cap       = vidioc_s_fmt_vbi_cap,
        .vidioc_g_audio             = vidioc_g_audio,
        .vidioc_s_audio             = vidioc_s_audio,
        .vidioc_cropcap             = vidioc_cropcap,
        .minor                      = -1,
 
        .tvnorms                    = V4L2_STD_ALL,
-       .current_norm               = V4L2_STD_PAL,
+       /* FIXME: we need this to be NTSC for VBI to work - it should
+          be moved to a per-board definition */
+       .current_norm               = V4L2_STD_NTSC,
 };
 
 static const struct v4l2_file_operations radio_fops = {