From: Hans Verkuil <hans.verk...@cisco.com>

Add support for the tw68 driver. The driver has been out-of-tree for many
years on gitorious: https://gitorious.org/tw68/tw68-v2

I have refactored and ported that driver to the latest V4L2 core frameworks.

Tested with my Techwell tw6805a and tw6816 grabber boards.

Signed-off-by: Hans Verkuil <hans.verk...@cisco.com>
---
 drivers/media/pci/Kconfig           |    1 +
 drivers/media/pci/Makefile          |    1 +
 drivers/media/pci/tw68/Kconfig      |   10 +
 drivers/media/pci/tw68/Makefile     |    3 +
 drivers/media/pci/tw68/tw68-core.c  |  448 +++++++++++++++
 drivers/media/pci/tw68/tw68-reg.h   |  195 +++++++
 drivers/media/pci/tw68/tw68-risc.c  |  230 ++++++++
 drivers/media/pci/tw68/tw68-video.c | 1062 +++++++++++++++++++++++++++++++++++
 drivers/media/pci/tw68/tw68.h       |  233 ++++++++
 9 files changed, 2183 insertions(+)
 create mode 100644 drivers/media/pci/tw68/Kconfig
 create mode 100644 drivers/media/pci/tw68/Makefile
 create mode 100644 drivers/media/pci/tw68/tw68-core.c
 create mode 100644 drivers/media/pci/tw68/tw68-reg.h
 create mode 100644 drivers/media/pci/tw68/tw68-risc.c
 create mode 100644 drivers/media/pci/tw68/tw68-video.c
 create mode 100644 drivers/media/pci/tw68/tw68.h

diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 5c16c9c..9332807 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -20,6 +20,7 @@ source "drivers/media/pci/ivtv/Kconfig"
 source "drivers/media/pci/zoran/Kconfig"
 source "drivers/media/pci/saa7146/Kconfig"
 source "drivers/media/pci/solo6x10/Kconfig"
+source "drivers/media/pci/tw68/Kconfig"
 endif
 
 if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index e5b53fb..e7f8060 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_VIDEO_CX88) += cx88/
 obj-$(CONFIG_VIDEO_BT848) += bt8xx/
 obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
 obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
+obj-$(CONFIG_VIDEO_TW68) += tw68/
 obj-$(CONFIG_VIDEO_MEYE) += meye/
 obj-$(CONFIG_STA2X11_VIP) += sta2x11/
 obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
diff --git a/drivers/media/pci/tw68/Kconfig b/drivers/media/pci/tw68/Kconfig
new file mode 100644
index 0000000..5425ba1
--- /dev/null
+++ b/drivers/media/pci/tw68/Kconfig
@@ -0,0 +1,10 @@
+config VIDEO_TW68
+       tristate "Techwell tw68x Video For Linux"
+       depends on VIDEO_DEV && PCI && VIDEO_V4L2
+       select I2C_ALGOBIT
+       select VIDEOBUF2_DMA_SG
+       ---help---
+         Support for Techwell tw68xx based frame grabber boards.
+
+         To compile this driver as a module, choose M here: the
+         module will be called tw68.
diff --git a/drivers/media/pci/tw68/Makefile b/drivers/media/pci/tw68/Makefile
new file mode 100644
index 0000000..3d02f28
--- /dev/null
+++ b/drivers/media/pci/tw68/Makefile
@@ -0,0 +1,3 @@
+tw68-objs := tw68-core.o tw68-video.o tw68-risc.o
+
+obj-$(CONFIG_VIDEO_TW68) += tw68.o
diff --git a/drivers/media/pci/tw68/tw68-core.c 
b/drivers/media/pci/tw68/tw68-core.c
new file mode 100644
index 0000000..2158646
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-core.c
@@ -0,0 +1,448 @@
+/*
+ *  tw68-core.c
+ *  Core functions for the Techwell 68xx driver
+ *
+ *  Much of this code is derived from the cx88 and sa7134 drivers, which
+ *  were in turn derived from the bt87x driver.  The original work was by
+ *  Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ *  Hans Verkuil, Andy Walls and many others.  Their work is gratefully
+ *  acknowledged.  Full credit goes to them - any problems within this code
+ *  are mine.
+ *
+ *  Copyright (C) 2009  William M. Brack <wbr...@mmm.com.hk>
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverk...@xs4all.nl>
+ *
+ *  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.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/sound.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm.h>
+
+#include <media/v4l2-dev.h>
+#include "tw68.h"
+#include "tw68-reg.h"
+
+MODULE_DESCRIPTION("v4l2 driver module for tw6800 based video capture cards");
+MODULE_AUTHOR("William M. Brack <wbr...@mmm.com.hk>");
+MODULE_LICENSE("GPL");
+
+static unsigned int latency = UNSET;
+module_param(latency, int, 0444);
+MODULE_PARM_DESC(latency, "pci latency timer");
+
+static unsigned int video_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+module_param_array(video_nr, int, NULL, 0444);
+MODULE_PARM_DESC(video_nr, "video device number");
+
+static unsigned int card[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+module_param_array(card, int, NULL, 0444);
+MODULE_PARM_DESC(card, "card type");
+
+static atomic_t tw68_instance = ATOMIC_INIT(0);
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * Please add any new PCI IDs to: http://pci-ids.ucw.cz.  This keeps
+ * the PCI ID database up to date.  Note that the entries must be
+ * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs.
+ */
+struct pci_device_id tw68_pci_tbl[] = {
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6800)},
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6801)},
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6804)},
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_1)},
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_2)},
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_3)},
+       {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_4)},
+       {0,}
+};
+
+/* ------------------------------------------------------------------ */
+
+
+/*
+ * The device is given a "soft reset". According to the specifications,
+ * after this "all register content remain unchanged", so we also write
+ * to all specified registers manually as well (mostly to manufacturer's
+ * specified reset values)
+ */
+static int tw68_hw_init1(struct tw68_dev *dev)
+{
+       /* Assure all interrupts are disabled */
+       tw_writel(TW68_INTMASK, 0);             /* 020 */
+       /* Clear any pending interrupts */
+       tw_writel(TW68_INTSTAT, 0xffffffff);    /* 01C */
+       /* Stop risc processor, set default buffer level */
+       tw_writel(TW68_DMAC, 0x1600);
+
+       tw_writeb(TW68_ACNTL, 0x80);    /* 218  soft reset */
+       msleep(100);
+
+       tw_writeb(TW68_INFORM, 0x40);   /* 208  mux0, 27mhz xtal */
+       tw_writeb(TW68_OPFORM, 0x04);   /* 20C  analog line-lock */
+       tw_writeb(TW68_HSYNC, 0);       /* 210  color-killer high sens */
+       tw_writeb(TW68_ACNTL, 0x42);    /* 218  int vref #2, chroma adc off */
+
+       tw_writeb(TW68_CROP_HI, 0x02);  /* 21C  Hactive m.s. bits */
+       tw_writeb(TW68_VDELAY_LO, 0x12);/* 220  Mfg specified reset value */
+       tw_writeb(TW68_VACTIVE_LO, 0xf0);
+       tw_writeb(TW68_HDELAY_LO, 0x0f);
+       tw_writeb(TW68_HACTIVE_LO, 0xd0);
+
+       tw_writeb(TW68_CNTRL1, 0xcd);   /* 230  Wide Chroma BPF B/W
+                                        *      Secam reduction, Adap comb for
+                                        *      NTSC, Op Mode 1 */
+
+       tw_writeb(TW68_VSCALE_LO, 0);   /* 234 */
+       tw_writeb(TW68_SCALE_HI, 0x11); /* 238 */
+       tw_writeb(TW68_HSCALE_LO, 0);   /* 23c */
+       tw_writeb(TW68_BRIGHT, 0);      /* 240 */
+       tw_writeb(TW68_CONTRAST, 0x5c); /* 244 */
+       tw_writeb(TW68_SHARPNESS, 0x51);/* 248 */
+       tw_writeb(TW68_SAT_U, 0x80);    /* 24C */
+       tw_writeb(TW68_SAT_V, 0x80);    /* 250 */
+       tw_writeb(TW68_HUE, 0x00);      /* 254 */
+
+       /* TODO - Check that none of these are set by control defaults */
+       tw_writeb(TW68_SHARP2, 0x53);   /* 258  Mfg specified reset val */
+       tw_writeb(TW68_VSHARP, 0x80);   /* 25C  Sharpness Coring val 8 */
+       tw_writeb(TW68_CORING, 0x44);   /* 260  CTI and Vert Peak coring */
+       tw_writeb(TW68_CNTRL2, 0x00);   /* 268  No power saving enabled */
+       tw_writeb(TW68_SDT, 0x07);      /* 270  Enable shadow reg, auto-det */
+       tw_writeb(TW68_SDTR, 0x7f);     /* 274  All stds recog, don't start */
+       tw_writeb(TW68_CLMPG, 0x50);    /* 280  Clamp end at 40 sys clocks */
+       tw_writeb(TW68_IAGC, 0x22);     /* 284  Mfg specified reset val */
+       tw_writeb(TW68_AGCGAIN, 0xf0);  /* 288  AGC gain when loop disabled */
+       tw_writeb(TW68_PEAKWT, 0xd8);   /* 28C  White peak threshold */
+       tw_writeb(TW68_CLMPL, 0x3c);    /* 290  Y channel clamp level */
+/*     tw_writeb(TW68_SYNCT, 0x38);*/  /* 294  Sync amplitude */
+       tw_writeb(TW68_SYNCT, 0x30);    /* 294  Sync amplitude */
+       tw_writeb(TW68_MISSCNT, 0x44);  /* 298  Horiz sync, VCR detect sens */
+       tw_writeb(TW68_PCLAMP, 0x28);   /* 29C  Clamp pos from PLL sync */
+       /* Bit DETV of VCNTL1 helps sync multi cams/chip board */
+       tw_writeb(TW68_VCNTL1, 0x04);   /* 2A0 */
+       tw_writeb(TW68_VCNTL2, 0);      /* 2A4 */
+       tw_writeb(TW68_CKILL, 0x68);    /* 2A8  Mfg specified reset val */
+       tw_writeb(TW68_COMB, 0x44);     /* 2AC  Mfg specified reset val */
+       tw_writeb(TW68_LDLY, 0x30);     /* 2B0  Max positive luma delay */
+       tw_writeb(TW68_MISC1, 0x14);    /* 2B4  Mfg specified reset val */
+       tw_writeb(TW68_LOOP, 0xa5);     /* 2B8  Mfg specified reset val */
+       tw_writeb(TW68_MISC2, 0xe0);    /* 2BC  Enable colour killer */
+       tw_writeb(TW68_MVSN, 0);        /* 2C0 */
+       tw_writeb(TW68_CLMD, 0x05);     /* 2CC  slice level auto, clamp med. */
+       tw_writeb(TW68_IDCNTL, 0);      /* 2D0  Writing zero to this register
+                                        *      selects NTSC ID detection,
+                                        *      but doesn't change the
+                                        *      sensitivity (which has a reset
+                                        *      value of 1E).  Since we are
+                                        *      not doing auto-detection, it
+                                        *      has no real effect */
+       tw_writeb(TW68_CLCNTL1, 0);     /* 2D4 */
+       tw_writel(TW68_VBIC, 0x03);     /* 010 */
+       tw_writel(TW68_CAP_CTL, 0x03);  /* 040  Enable both even & odd flds */
+       tw_writel(TW68_DMAC, 0x2000);   /* patch set had 0x2080 */
+       tw_writel(TW68_TESTREG, 0);     /* 02C */
+
+       /*
+        * Some common boards, especially inexpensive single-chip models,
+        * use the GPIO bits 0-3 to control an on-board video-output mux.
+        * For these boards, we need to set up the GPIO register into
+        * "normal" mode, set bits 0-3 as output, and then set those bits
+        * zero.
+        *
+        * Eventually, it would be nice if we could identify these boards
+        * uniquely, and only do this initialisation if the board has been
+        * identify.  For the moment, however, it shouldn't hurt anything
+        * to do these steps.
+        */
+       tw_writel(TW68_GPIOC, 0);       /* Set the GPIO to "normal", no ints */
+       tw_writel(TW68_GPOE, 0x0f);     /* Set bits 0-3 to "output" */
+       tw_writel(TW68_GPDATA, 0);      /* Set all bits to low state */
+
+       /* Initialize the device control structures */
+       mutex_init(&dev->lock);
+       spin_lock_init(&dev->slock);
+
+       /* Initialize any subsystems */
+       tw68_video_init1(dev);
+       return 0;
+}
+
+static irqreturn_t tw68_irq(int irq, void *dev_id)
+{
+       struct tw68_dev *dev = dev_id;
+       u32 status, orig;
+       int loop;
+
+       status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
+       /* Check if anything to do */
+       if (0 == status)
+               return IRQ_NONE;        /* Nope - return */
+       for (loop = 0; loop < 10; loop++) {
+               if (status & dev->board_virqmask)       /* video interrupt */
+                       tw68_irq_video_done(dev, status);
+               status = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
+               if (0 == status)
+                       return IRQ_HANDLED;
+       }
+       dev_dbg(&dev->pci_dev->dev, "%s: **** INTERRUPT NOT HANDLED - clearing 
mask (orig 0x%08x, cur 0x%08x)",
+                       dev->name, orig, tw_readl(TW68_INTSTAT));
+       dev_dbg(&dev->pci_dev->dev, "%s: pci_irqmask 0x%08x; board_virqmask 
0x%08x ****\n",
+                       dev->name, dev->pci_irqmask, dev->board_virqmask);
+       tw_clearl(TW68_INTMASK, dev->pci_irqmask);
+       return IRQ_HANDLED;
+}
+
+static int tw68_initdev(struct pci_dev *pci_dev,
+                                    const struct pci_device_id *pci_id)
+{
+       struct tw68_dev *dev;
+       int vidnr = -1;
+       int err;
+
+       dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL);
+       if (NULL == dev)
+               return -ENOMEM;
+
+       dev->instance = v4l2_device_set_name(&dev->v4l2_dev, "tw68",
+                                               &tw68_instance);
+
+       err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
+       if (err)
+               return err;
+
+       /* pci init */
+       dev->pci = pci_dev;
+       if (pci_enable_device(pci_dev)) {
+               err = -EIO;
+               goto fail1;
+       }
+
+       dev->name = dev->v4l2_dev.name;
+
+       if (UNSET != latency) {
+               pr_info("%s: setting pci latency timer to %d\n",
+                      dev->name, latency);
+               pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
+       }
+
+       /* print pci info */
+       pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev);
+       pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER,  &dev->pci_lat);
+       pr_info("%s: found at %s, rev: %d, irq: %d, latency: %d, mmio: 
0x%llx\n",
+               dev->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
+               dev->pci_lat, (u64)pci_resource_start(pci_dev, 0));
+       pci_set_master(pci_dev);
+       if (!pci_dma_supported(pci_dev, DMA_BIT_MASK(32))) {
+               pr_info("%s: Oops: no 32bit PCI DMA ???\n", dev->name);
+               err = -EIO;
+               goto fail1;
+       }
+
+       switch (pci_id->device) {
+       case PCI_DEVICE_ID_6800:        /* TW6800 */
+               dev->vdecoder = TW6800;
+               dev->board_virqmask = TW68_VID_INTS;
+               break;
+       case PCI_DEVICE_ID_6801:        /* Video decoder for TW6802 */
+               dev->vdecoder = TW6801;
+               dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
+               break;
+       case PCI_DEVICE_ID_6804:        /* Video decoder for TW6804 */
+               dev->vdecoder = TW6804;
+               dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
+               break;
+       default:
+               dev->vdecoder = TWXXXX; /* To be announced */
+               dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
+               break;
+       }
+
+       /* get mmio */
+       if (!request_mem_region(pci_resource_start(pci_dev, 0),
+                               pci_resource_len(pci_dev, 0),
+                               dev->name)) {
+               err = -EBUSY;
+               pr_err("%s: can't get MMIO memory @ 0x%llx\n",
+                       dev->name,
+                       (unsigned long long)pci_resource_start(pci_dev, 0));
+               goto fail1;
+       }
+       dev->lmmio = ioremap(pci_resource_start(pci_dev, 0),
+                            pci_resource_len(pci_dev, 0));
+       dev->bmmio = (__u8 __iomem *)dev->lmmio;
+       if (NULL == dev->lmmio) {
+               err = -EIO;
+               pr_err("%s: can't ioremap() MMIO memory\n",
+                      dev->name);
+               goto fail2;
+       }
+       /* initialize hardware #1 */
+       /* Then do any initialisation wanted before interrupts are on */
+       tw68_hw_init1(dev);
+
+       /* get irq */
+       err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw68_irq,
+                         IRQF_SHARED | IRQF_DISABLED, dev->name, dev);
+       if (err < 0) {
+               pr_err("%s: can't get IRQ %d\n",
+                      dev->name, pci_dev->irq);
+               goto fail3;
+       }
+
+       /*
+        *  Now do remainder of initialisation, first for
+        *  things unique for this card, then for general board
+        */
+       if (dev->instance < TW68_MAXBOARDS)
+               vidnr = video_nr[dev->instance];
+       /* initialise video function first */
+       err = tw68_video_init2(dev, vidnr);
+       if (err < 0) {
+               pr_err("%s: can't register video device\n",
+                      dev->name);
+               goto fail4;
+       }
+       tw_setl(TW68_INTMASK, dev->pci_irqmask);
+
+       pr_info("%s: registered device %s\n",
+              dev->name, video_device_node_name(&dev->vdev));
+
+       return 0;
+
+fail4:
+       video_unregister_device(&dev->vdev);
+fail3:
+       iounmap(dev->lmmio);
+fail2:
+       release_mem_region(pci_resource_start(pci_dev, 0),
+                          pci_resource_len(pci_dev, 0));
+fail1:
+       v4l2_device_unregister(&dev->v4l2_dev);
+       return err;
+}
+
+static void tw68_finidev(struct pci_dev *pci_dev)
+{
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct tw68_dev *dev =
+               container_of(v4l2_dev, struct tw68_dev, v4l2_dev);
+
+       /* shutdown subsystems */
+       tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+       tw_writel(TW68_INTMASK, 0);
+
+       /* unregister */
+       video_unregister_device(&dev->vdev);
+       v4l2_ctrl_handler_free(&dev->hdl);
+
+       /* release resources */
+       iounmap(dev->lmmio);
+       release_mem_region(pci_resource_start(pci_dev, 0),
+                          pci_resource_len(pci_dev, 0));
+
+       v4l2_device_unregister(&dev->v4l2_dev);
+}
+
+#ifdef CONFIG_PM
+
+static int tw68_suspend(struct pci_dev *pci_dev , pm_message_t state)
+{
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct tw68_dev *dev = container_of(v4l2_dev,
+                               struct tw68_dev, v4l2_dev);
+
+       tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+       dev->pci_irqmask &= ~TW68_VID_INTS;
+       tw_writel(TW68_INTMASK, 0);
+
+       synchronize_irq(pci_dev->irq);
+
+       pci_save_state(pci_dev);
+       pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state));
+       vb2_discard_done(&dev->vidq);
+
+       return 0;
+}
+
+static int tw68_resume(struct pci_dev *pci_dev)
+{
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct tw68_dev *dev = container_of(v4l2_dev,
+                                           struct tw68_dev, v4l2_dev);
+       struct tw68_buf *buf;
+       unsigned long flags;
+
+       pci_set_power_state(pci_dev, PCI_D0);
+       pci_restore_state(pci_dev);
+
+       /* Do things that are done in tw68_initdev ,
+               except of initializing memory structures.*/
+
+       msleep(100);
+
+       tw68_set_tvnorm_hw(dev);
+
+       /*resume unfinished buffer(s)*/
+       spin_lock_irqsave(&dev->slock, flags);
+       buf = container_of(dev->active.next, struct tw68_buf, list);
+
+       tw68_video_start_dma(dev, buf);
+
+       spin_unlock_irqrestore(&dev->slock, flags);
+
+       return 0;
+}
+#endif
+
+/* ----------------------------------------------------------- */
+
+static struct pci_driver tw68_pci_driver = {
+       .name     = "tw68",
+       .id_table = tw68_pci_tbl,
+       .probe    = tw68_initdev,
+       .remove   = tw68_finidev,
+#ifdef CONFIG_PM
+       .suspend  = tw68_suspend,
+       .resume   = tw68_resume
+#endif
+};
+
+static int tw68_init(void)
+{
+       pr_info("tw68: v4l2 driver version %d.%d.%d loaded\n",
+               (TW68_VERSION_CODE >> 16) & 0xff,
+               (TW68_VERSION_CODE >> 8) & 0xff,
+               TW68_VERSION_CODE & 0xff);
+       return pci_register_driver(&tw68_pci_driver);
+}
+
+static void module_cleanup(void)
+{
+       pci_unregister_driver(&tw68_pci_driver);
+}
+
+module_init(tw68_init);
+module_exit(module_cleanup);
diff --git a/drivers/media/pci/tw68/tw68-reg.h 
b/drivers/media/pci/tw68/tw68-reg.h
new file mode 100644
index 0000000..cf494dc
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-reg.h
@@ -0,0 +1,195 @@
+/*
+ *  tw68-reg.h - TW68xx register offsets
+ *
+ *  Much of this code is derived from the cx88 and sa7134 drivers, which
+ *  were in turn derived from the bt87x driver.  The original work was by
+ *  Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ *  Hans Verkuil, Andy Walls and many others.  Their work is gratefully
+ *  acknowledged.  Full credit goes to them - any problems within this code
+ *  are mine.
+ *
+ *  Copyright (C) William M. Brack <wbr...@mmm.com.hk>
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverk...@xs4all.nl>
+ *
+ *  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.
+*/
+
+#ifndef _TW68_REG_H_
+#define _TW68_REG_H_
+
+/* ---------------------------------------------------------------------- */
+#define        TW68_DMAC               0x000
+#define        TW68_DMAP_SA            0x004
+#define        TW68_DMAP_EXE           0x008
+#define        TW68_DMAP_PP            0x00c
+#define        TW68_VBIC               0x010
+#define        TW68_SBUSC              0x014
+#define        TW68_SBUSSD             0x018
+#define        TW68_INTSTAT            0x01C
+#define        TW68_INTMASK            0x020
+#define        TW68_GPIOC              0x024
+#define        TW68_GPOE               0x028
+#define        TW68_TESTREG            0x02C
+#define        TW68_SBUSRD             0x030
+#define        TW68_SBUS_TRIG          0x034
+#define        TW68_CAP_CTL            0x040
+#define        TW68_SUBSYS             0x054
+#define        TW68_I2C_RST            0x064
+#define        TW68_VBIINST            0x06C
+/* define bits in FIFO and DMAP Control reg */
+#define        TW68_DMAP_EN            (1 << 0)
+#define        TW68_FIFO_EN            (1 << 1)
+/* define the Interrupt Status Register bits */
+#define        TW68_SBDONE             (1 << 0)
+#define        TW68_DMAPI              (1 << 1)
+#define        TW68_GPINT              (1 << 2)
+#define        TW68_FFOF               (1 << 3)
+#define        TW68_FDMIS              (1 << 4)
+#define        TW68_DMAPERR            (1 << 5)
+#define        TW68_PABORT             (1 << 6)
+#define        TW68_SBDONE2            (1 << 12)
+#define        TW68_SBERR2             (1 << 13)
+#define        TW68_PPERR              (1 << 14)
+#define        TW68_FFERR              (1 << 15)
+#define        TW68_DET50              (1 << 16)
+#define        TW68_FLOCK              (1 << 17)
+#define        TW68_CCVALID            (1 << 18)
+#define        TW68_VLOCK              (1 << 19)
+#define        TW68_FIELD              (1 << 20)
+#define        TW68_SLOCK              (1 << 21)
+#define        TW68_HLOCK              (1 << 22)
+#define        TW68_VDLOSS             (1 << 23)
+#define        TW68_SBERR              (1 << 24)
+/* define the i2c control register bits */
+#define        TW68_SBMODE             (0)
+#define        TW68_WREN               (1)
+#define        TW68_SSCLK              (6)
+#define        TW68_SSDAT              (7)
+#define        TW68_SBCLK              (8)
+#define        TW68_WDLEN              (16)
+#define        TW68_RDLEN              (20)
+#define        TW68_SBRW               (24)
+#define        TW68_SBDEV              (25)
+
+#define        TW68_SBMODE_B           (1 << TW68_SBMODE)
+#define        TW68_WREN_B             (1 << TW68_WREN)
+#define        TW68_SSCLK_B            (1 << TW68_SSCLK)
+#define        TW68_SSDAT_B            (1 << TW68_SSDAT)
+#define        TW68_SBRW_B             (1 << TW68_SBRW)
+
+#define        TW68_GPDATA             0x100
+#define        TW68_STATUS1            0x204
+#define        TW68_INFORM             0x208
+#define        TW68_OPFORM             0x20C
+#define        TW68_HSYNC              0x210
+#define        TW68_ACNTL              0x218
+#define        TW68_CROP_HI            0x21C
+#define        TW68_VDELAY_LO          0x220
+#define        TW68_VACTIVE_LO         0x224
+#define        TW68_HDELAY_LO          0x228
+#define        TW68_HACTIVE_LO         0x22C
+#define        TW68_CNTRL1             0x230
+#define        TW68_VSCALE_LO          0x234
+#define        TW68_SCALE_HI           0x238
+#define        TW68_HSCALE_LO          0x23C
+#define        TW68_BRIGHT             0x240
+#define        TW68_CONTRAST           0x244
+#define        TW68_SHARPNESS          0x248
+#define        TW68_SAT_U              0x24C
+#define        TW68_SAT_V              0x250
+#define        TW68_HUE                0x254
+#define        TW68_SHARP2             0x258
+#define        TW68_VSHARP             0x25C
+#define        TW68_CORING             0x260
+#define        TW68_VBICNTL            0x264
+#define        TW68_CNTRL2             0x268
+#define        TW68_CC_DATA            0x26C
+#define        TW68_SDT                0x270
+#define        TW68_SDTR               0x274
+#define        TW68_RESERV2            0x278
+#define        TW68_RESERV3            0x27C
+#define        TW68_CLMPG              0x280
+#define        TW68_IAGC               0x284
+#define        TW68_AGCGAIN            0x288
+#define        TW68_PEAKWT             0x28C
+#define        TW68_CLMPL              0x290
+#define        TW68_SYNCT              0x294
+#define        TW68_MISSCNT            0x298
+#define        TW68_PCLAMP             0x29C
+#define        TW68_VCNTL1             0x2A0
+#define        TW68_VCNTL2             0x2A4
+#define        TW68_CKILL              0x2A8
+#define        TW68_COMB               0x2AC
+#define        TW68_LDLY               0x2B0
+#define        TW68_MISC1              0x2B4
+#define        TW68_LOOP               0x2B8
+#define        TW68_MISC2              0x2BC
+#define        TW68_MVSN               0x2C0
+#define        TW68_STATUS2            0x2C4
+#define        TW68_HFREF              0x2C8
+#define        TW68_CLMD               0x2CC
+#define        TW68_IDCNTL             0x2D0
+#define        TW68_CLCNTL1            0x2D4
+
+/* Audio */
+#define        TW68_ACKI1              0x300
+#define        TW68_ACKI2              0x304
+#define        TW68_ACKI3              0x308
+#define        TW68_ACKN1              0x30C
+#define        TW68_ACKN2              0x310
+#define        TW68_ACKN3              0x314
+#define        TW68_SDIV               0x318
+#define        TW68_LRDIV              0x31C
+#define        TW68_ACCNTL             0x320
+
+#define        TW68_VSCTL              0x3B8
+#define        TW68_CHROMAGVAL         0x3BC
+
+#define        TW68_F2CROP_HI          0x3DC
+#define        TW68_F2VDELAY_LO        0x3E0
+#define        TW68_F2VACTIVE_LO       0x3E4
+#define        TW68_F2HDELAY_LO        0x3E8
+#define        TW68_F2HACTIVE_LO       0x3EC
+#define        TW68_F2CNT              0x3F0
+#define        TW68_F2VSCALE_LO        0x3F4
+#define        TW68_F2SCALE_HI         0x3F8
+#define        TW68_F2HSCALE_LO        0x3FC
+
+#define        RISC_INT_BIT            0x08000000
+#define        RISC_SYNCO              0xC0000000
+#define        RISC_SYNCE              0xD0000000
+#define        RISC_JUMP               0xB0000000
+#define        RISC_LINESTART          0x90000000
+#define        RISC_INLINE             0xA0000000
+
+#define VideoFormatNTSC                 0
+#define VideoFormatNTSCJapan    0
+#define VideoFormatPALBDGHI     1
+#define VideoFormatSECAM        2
+#define VideoFormatNTSC443      3
+#define VideoFormatPALM                 4
+#define VideoFormatPALN                 5
+#define VideoFormatPALNC        5
+#define VideoFormatPAL60        6
+#define VideoFormatAuto                 7
+
+#define ColorFormatRGB32        0x00
+#define ColorFormatRGB24        0x10
+#define ColorFormatRGB16        0x20
+#define ColorFormatRGB15        0x30
+#define ColorFormatYUY2                 0x40
+#define ColorFormatBSWAP         0x04
+#define ColorFormatWSWAP         0x08
+#define ColorFormatGamma         0x80
+#endif
diff --git a/drivers/media/pci/tw68/tw68-risc.c 
b/drivers/media/pci/tw68/tw68-risc.c
new file mode 100644
index 0000000..ea75e74
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-risc.c
@@ -0,0 +1,230 @@
+/*
+ *  tw68_risc.c
+ *  Part of the device driver for Techwell 68xx based cards
+ *
+ *  Much of this code is derived from the cx88 and sa7134 drivers, which
+ *  were in turn derived from the bt87x driver.  The original work was by
+ *  Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ *  Hans Verkuil, Andy Walls and many others.  Their work is gratefully
+ *  acknowledged.  Full credit goes to them - any problems within this code
+ *  are mine.
+ *
+ *  Copyright (C) 2009  William M. Brack <wbr...@mmm.com.hk>
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverk...@xs4all.nl>
+ *
+ *  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.
+ */
+
+#include "tw68.h"
+
+/**
+ *  @rp                pointer to current risc program position
+ *  @sglist    pointer to "scatter-gather list" of buffer pointers
+ *  @offset    offset to target memory buffer
+ *  @sync_line 0 -> no sync, 1 -> odd sync, 2 -> even sync
+ *  @bpl       number of bytes per scan line
+ *  @padding   number of bytes of padding to add
+ *  @lines     number of lines in field
+ *  @jump      insert a jump at the start
+ */
+static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
+                           unsigned int offset, u32 sync_line,
+                           unsigned int bpl, unsigned int padding,
+                           unsigned int lines, bool jump)
+{
+       struct scatterlist *sg;
+       unsigned int line, todo, done;
+
+       if (jump) {
+               *(rp++) = cpu_to_le32(RISC_JUMP);
+               *(rp++) = 0;
+       }
+
+       /* sync instruction */
+       if (sync_line == 1)
+               *(rp++) = cpu_to_le32(RISC_SYNCO);
+       else
+               *(rp++) = cpu_to_le32(RISC_SYNCE);
+       *(rp++) = 0;
+
+       /* scan lines */
+       sg = sglist;
+       for (line = 0; line < lines; line++) {
+               /* calculate next starting position */
+               while (offset && offset >= sg_dma_len(sg)) {
+                       offset -= sg_dma_len(sg);
+                       sg = sg_next(sg);
+               }
+               if (bpl <= sg_dma_len(sg) - offset) {
+                       /* fits into current chunk */
+                       *(rp++) = cpu_to_le32(RISC_LINESTART |
+                                             /* (offset<<12) |*/  bpl);
+                       *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
+                       offset += bpl;
+               } else {
+                       /*
+                        * scanline needs to be split.  Put the start in
+                        * whatever memory remains using RISC_LINESTART,
+                        * then the remainder into following addresses
+                        * given by the scatter-gather list.
+                        */
+                       todo = bpl;     /* one full line to be done */
+                       /* first fragment */
+                       done = (sg_dma_len(sg) - offset);
+                       *(rp++) = cpu_to_le32(RISC_LINESTART |
+                                               (7 << 24) |
+                                               done);
+                       *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
+                       todo -= done;
+                       sg = sg_next(sg);
+                       /* succeeding fragments have no offset */
+                       while (todo > sg_dma_len(sg)) {
+                               *(rp++) = cpu_to_le32(RISC_INLINE |
+                                               (done << 12) |
+                                               sg_dma_len(sg));
+                               *(rp++) = cpu_to_le32(sg_dma_address(sg));
+                               todo -= sg_dma_len(sg);
+                               sg = sg_next(sg);
+                               done += sg_dma_len(sg);
+                       }
+                       if (todo) {
+                               /* final chunk - offset 0, count 'todo' */
+                               *(rp++) = cpu_to_le32(RISC_INLINE |
+                                                       (done << 12) |
+                                                       todo);
+                               *(rp++) = cpu_to_le32(sg_dma_address(sg));
+                       }
+                       offset = todo;
+               }
+               offset += padding;
+       }
+
+       return rp;
+}
+
+/**
+ * tw68_risc_buffer
+ *
+ *     This routine is called by tw68-video.  It allocates
+ *     memory for the dma controller "program" and then fills in that
+ *     memory with the appropriate "instructions".
+ *
+ *     @pci_dev        structure with info about the pci
+ *                     slot which our device is in.
+ *     @risc           structure with info about the memory
+ *                     used for our controller program.
+ *     @sglist         scatter-gather list entry
+ *     @top_offset     offset within the risc program area for the
+ *                     first odd frame line
+ *     @bottom_offset  offset within the risc program area for the
+ *                     first even frame line
+ *     @bpl            number of data bytes per scan line
+ *     @padding        number of extra bytes to add at end of line
+ *     @lines          number of scan lines
+ */
+int tw68_risc_buffer(struct pci_dev *pci,
+                       struct tw68_buf *buf,
+                       struct scatterlist *sglist,
+                       unsigned int top_offset,
+                       unsigned int bottom_offset,
+                       unsigned int bpl,
+                       unsigned int padding,
+                       unsigned int lines)
+{
+       u32 instructions, fields;
+       __le32 *rp;
+
+       fields = 0;
+       if (UNSET != top_offset)
+               fields++;
+       if (UNSET != bottom_offset)
+               fields++;
+       /*
+        * estimate risc mem: worst case is one write per page border +
+        * one write per scan line + syncs + 2 jumps (all 2 dwords).
+        * Padding can cause next bpl to start close to a page border.
+        * First DMA region may be smaller than PAGE_SIZE
+        */
+       instructions  = fields * (1 + (((bpl + padding) * lines) /
+                        PAGE_SIZE) + lines) + 4;
+       buf->size = instructions * 8;
+       buf->cpu = pci_alloc_consistent(pci, buf->size, &buf->dma);
+       if (buf->cpu == NULL)
+               return -ENOMEM;
+
+       /* write risc instructions */
+       rp = buf->cpu;
+       if (UNSET != top_offset)        /* generates SYNCO */
+               rp = tw68_risc_field(rp, sglist, top_offset, 1,
+                                    bpl, padding, lines, true);
+       if (UNSET != bottom_offset)     /* generates SYNCE */
+               rp = tw68_risc_field(rp, sglist, bottom_offset, 2,
+                                    bpl, padding, lines, top_offset == UNSET);
+
+       /* save pointer to jmp instruction address */
+       buf->jmp = rp;
+       buf->cpu[1] = cpu_to_le32(buf->dma + 8);
+       /* assure risc buffer hasn't overflowed */
+       BUG_ON((buf->jmp - buf->cpu + 2) * sizeof(buf->cpu[0]) > buf->size);
+       return 0;
+}
+
+#if 0
+/* ------------------------------------------------------------------ */
+/* debug helper code                                                  */
+
+static void tw68_risc_decode(u32 risc, u32 addr)
+{
+#define        RISC_OP(reg)    (((reg) >> 28) & 7)
+       static struct instr_details {
+               char *name;
+               u8 has_data_type;
+               u8 has_byte_info;
+               u8 has_addr;
+       } instr[8] = {
+               [RISC_OP(RISC_SYNCO)]     = {"syncOdd", 0, 0, 0},
+               [RISC_OP(RISC_SYNCE)]     = {"syncEven", 0, 0, 0},
+               [RISC_OP(RISC_JUMP)]      = {"jump", 0, 0, 1},
+               [RISC_OP(RISC_LINESTART)] = {"lineStart", 1, 1, 1},
+               [RISC_OP(RISC_INLINE)]    = {"inline", 1, 1, 1},
+       };
+       u32 p;
+
+       p = RISC_OP(risc);
+       if (!(risc & 0x80000000) || !instr[p].name) {
+               pr_debug("0x%08x [ INVALID ]\n", risc);
+               return;
+       }
+       pr_debug("0x%08x %-9s IRQ=%d",
+               risc, instr[p].name, (risc >> 27) & 1);
+       if (instr[p].has_data_type)
+               pr_debug(" Type=%d", (risc >> 24) & 7);
+       if (instr[p].has_byte_info)
+               pr_debug(" Start=0x%03x Count=%03u",
+                       (risc >> 12) & 0xfff, risc & 0xfff);
+       if (instr[p].has_addr)
+               pr_debug(" StartAddr=0x%08x", addr);
+       pr_debug("\n");
+}
+
+void tw68_risc_program_dump(struct tw68_core *core, struct tw68_buf *buf)
+{
+       const __le32 *addr;
+
+       pr_debug("%s: risc_program_dump: risc=%p, buf->cpu=0x%p, 
buf->jmp=0x%p\n",
+                 core->name, buf, buf->cpu, buf->jmp);
+       for (addr = buf->cpu; addr <= buf->jmp; addr += 2)
+               tw68_risc_decode(*addr, *(addr+1));
+}
+#endif
diff --git a/drivers/media/pci/tw68/tw68-video.c 
b/drivers/media/pci/tw68/tw68-video.c
new file mode 100644
index 0000000..0a2d120
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-video.c
@@ -0,0 +1,1062 @@
+/*
+ *  tw68 functions to handle video data
+ *
+ *  Much of this code is derived from the cx88 and sa7134 drivers, which
+ *  were in turn derived from the bt87x driver.  The original work was by
+ *  Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ *  Hans Verkuil, Andy Walls and many others.  Their work is gratefully
+ *  acknowledged.  Full credit goes to them - any problems within this code
+ *  are mine.
+ *
+ *  Copyright (C) 2009  William M. Brack <wbr...@mmm.com.hk>
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverk...@xs4all.nl>
+ *
+ *  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.
+ */
+
+#include <linux/module.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "tw68.h"
+#include "tw68-reg.h"
+
+/* ------------------------------------------------------------------ */
+/* data structs for video                                             */
+/*
+ * FIXME -
+ * Note that the saa7134 has formats, e.g. YUV420, which are classified
+ * as "planar".  These affect overlay mode, and are flagged with a field
+ * ".planar" in the format.  Do we need to implement this in this driver?
+ */
+static const struct tw68_format formats[] = {
+       {
+               .name           = "15 bpp RGB, le",
+               .fourcc         = V4L2_PIX_FMT_RGB555,
+               .depth          = 16,
+               .twformat       = ColorFormatRGB15,
+       }, {
+               .name           = "15 bpp RGB, be",
+               .fourcc         = V4L2_PIX_FMT_RGB555X,
+               .depth          = 16,
+               .twformat       = ColorFormatRGB15 | ColorFormatBSWAP,
+       }, {
+               .name           = "16 bpp RGB, le",
+               .fourcc         = V4L2_PIX_FMT_RGB565,
+               .depth          = 16,
+               .twformat       = ColorFormatRGB16,
+       }, {
+               .name           = "16 bpp RGB, be",
+               .fourcc         = V4L2_PIX_FMT_RGB565X,
+               .depth          = 16,
+               .twformat       = ColorFormatRGB16 | ColorFormatBSWAP,
+       }, {
+               .name           = "24 bpp RGB, le",
+               .fourcc         = V4L2_PIX_FMT_BGR24,
+               .depth          = 24,
+               .twformat       = ColorFormatRGB24,
+       }, {
+               .name           = "24 bpp RGB, be",
+               .fourcc         = V4L2_PIX_FMT_RGB24,
+               .depth          = 24,
+               .twformat       = ColorFormatRGB24 | ColorFormatBSWAP,
+       }, {
+               .name           = "32 bpp RGB, le",
+               .fourcc         = V4L2_PIX_FMT_BGR32,
+               .depth          = 32,
+               .twformat       = ColorFormatRGB32,
+       }, {
+               .name           = "32 bpp RGB, be",
+               .fourcc         = V4L2_PIX_FMT_RGB32,
+               .depth          = 32,
+               .twformat       = ColorFormatRGB32 | ColorFormatBSWAP |
+                                 ColorFormatWSWAP,
+       }, {
+               .name           = "4:2:2 packed, YUYV",
+               .fourcc         = V4L2_PIX_FMT_YUYV,
+               .depth          = 16,
+               .twformat       = ColorFormatYUY2,
+       }, {
+               .name           = "4:2:2 packed, UYVY",
+               .fourcc         = V4L2_PIX_FMT_UYVY,
+               .depth          = 16,
+               .twformat       = ColorFormatYUY2 | ColorFormatBSWAP,
+       }
+};
+#define FORMATS ARRAY_SIZE(formats)
+
+#define NORM_625_50                    \
+               .h_delay        = 3,    \
+               .h_delay0       = 133,  \
+               .h_start        = 0,    \
+               .h_stop         = 719,  \
+               .v_delay        = 24,   \
+               .vbi_v_start_0  = 7,    \
+               .vbi_v_stop_0   = 22,   \
+               .video_v_start  = 24,   \
+               .video_v_stop   = 311,  \
+               .vbi_v_start_1  = 319
+
+#define NORM_525_60                    \
+               .h_delay        = 8,    \
+               .h_delay0       = 138,  \
+               .h_start        = 0,    \
+               .h_stop         = 719,  \
+               .v_delay        = 22,   \
+               .vbi_v_start_0  = 10,   \
+               .vbi_v_stop_0   = 21,   \
+               .video_v_start  = 22,   \
+               .video_v_stop   = 262,  \
+               .vbi_v_start_1  = 273
+
+/*
+ * The following table is searched by tw68_s_std, first for a specific
+ * match, then for an entry which contains the desired id.  The table
+ * entries should therefore be ordered in ascending order of specificity.
+ */
+static const struct tw68_tvnorm tvnorms[] = {
+       {
+               .name           = "PAL", /* autodetect */
+               .id             = V4L2_STD_PAL,
+               NORM_625_50,
+
+               .sync_control   = 0x18,
+               .luma_control   = 0x40,
+               .chroma_ctrl1   = 0x81,
+               .chroma_gain    = 0x2a,
+               .chroma_ctrl2   = 0x06,
+               .vgate_misc     = 0x1c,
+               .format         = VideoFormatPALBDGHI,
+       }, {
+               .name           = "NTSC",
+               .id             = V4L2_STD_NTSC,
+               NORM_525_60,
+
+               .sync_control   = 0x59,
+               .luma_control   = 0x40,
+               .chroma_ctrl1   = 0x89,
+               .chroma_gain    = 0x2a,
+               .chroma_ctrl2   = 0x0e,
+               .vgate_misc     = 0x18,
+               .format         = VideoFormatNTSC,
+       }, {
+               .name           = "SECAM",
+               .id             = V4L2_STD_SECAM,
+               NORM_625_50,
+
+               .sync_control   = 0x18,
+               .luma_control   = 0x1b,
+               .chroma_ctrl1   = 0xd1,
+               .chroma_gain    = 0x80,
+               .chroma_ctrl2   = 0x00,
+               .vgate_misc     = 0x1c,
+               .format         = VideoFormatSECAM,
+       }, {
+               .name           = "PAL-M",
+               .id             = V4L2_STD_PAL_M,
+               NORM_525_60,
+
+               .sync_control   = 0x59,
+               .luma_control   = 0x40,
+               .chroma_ctrl1   = 0xb9,
+               .chroma_gain    = 0x2a,
+               .chroma_ctrl2   = 0x0e,
+               .vgate_misc     = 0x18,
+               .format         = VideoFormatPALM,
+       }, {
+               .name           = "PAL-Nc",
+               .id             = V4L2_STD_PAL_Nc,
+               NORM_625_50,
+
+               .sync_control   = 0x18,
+               .luma_control   = 0x40,
+               .chroma_ctrl1   = 0xa1,
+               .chroma_gain    = 0x2a,
+               .chroma_ctrl2   = 0x06,
+               .vgate_misc     = 0x1c,
+               .format         = VideoFormatPALNC,
+       }, {
+               .name           = "PAL-60",
+               .id             = V4L2_STD_PAL_60,
+               .h_delay        = 186,
+               .h_start        = 0,
+               .h_stop         = 719,
+               .v_delay        = 26,
+               .video_v_start  = 23,
+               .video_v_stop   = 262,
+               .vbi_v_start_0  = 10,
+               .vbi_v_stop_0   = 21,
+               .vbi_v_start_1  = 273,
+
+               .sync_control   = 0x18,
+               .luma_control   = 0x40,
+               .chroma_ctrl1   = 0x81,
+               .chroma_gain    = 0x2a,
+               .chroma_ctrl2   = 0x06,
+               .vgate_misc     = 0x1c,
+               .format         = VideoFormatPAL60,
+       }
+};
+#define TVNORMS ARRAY_SIZE(tvnorms)
+
+static const struct tw68_format *format_by_fourcc(unsigned int fourcc)
+{
+       unsigned int i;
+
+       for (i = 0; i < FORMATS; i++)
+               if (formats[i].fourcc == fourcc)
+                       return formats+i;
+       return NULL;
+}
+
+
+/* ------------------------------------------------------------------ */
+/*
+ * Note that the cropping rectangles are described in terms of a single
+ * frame, i.e. line positions are only 1/2 the interlaced equivalent
+ */
+static void set_tvnorm(struct tw68_dev *dev, const struct tw68_tvnorm *norm)
+{
+       if (norm != dev->tvnorm) {
+               dev->width = 720;
+               dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576;
+               dev->tvnorm = norm;
+               tw68_set_tvnorm_hw(dev);
+       }
+}
+
+/*
+ * tw68_set_scale
+ *
+ * Scaling and Cropping for video decoding
+ *
+ * We are working with 3 values for horizontal and vertical - scale,
+ * delay and active.
+ *
+ * HACTIVE represent the actual number of pixels in the "usable" image,
+ * before scaling.  HDELAY represents the number of pixels skipped
+ * between the start of the horizontal sync and the start of the image.
+ * HSCALE is calculated using the formula
+ *     HSCALE = (HACTIVE / (#pixels desired)) * 256
+ *
+ * The vertical registers are similar, except based upon the total number
+ * of lines in the image, and the first line of the image (i.e. ignoring
+ * vertical sync and VBI).
+ *
+ * Note that the number of bytes reaching the FIFO (and hence needing
+ * to be processed by the DMAP program) is completely dependent upon
+ * these values, especially HSCALE.
+ *
+ * Parameters:
+ *     @dev            pointer to the device structure, needed for
+ *                     getting current norm (as well as debug print)
+ *     @width          actual image width (from user buffer)
+ *     @height         actual image height
+ *     @field          indicates Top, Bottom or Interlaced
+ */
+static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
+                         unsigned int height, enum v4l2_field field)
+{
+       const struct tw68_tvnorm *norm = dev->tvnorm;
+       /* set individually for debugging clarity */
+       int hactive, hdelay, hscale;
+       int vactive, vdelay, vscale;
+       int comb;
+
+       if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */
+               height /= 2;            /* we must set for 1-frame */
+
+       pr_debug("%s: width=%d, height=%d, both=%d\n"
+                "  tvnorm h_delay=%d, h_start=%d, h_stop=%d, "
+                "v_delay=%d, v_start=%d, v_stop=%d\n" , __func__,
+               width, height, V4L2_FIELD_HAS_BOTH(field),
+               norm->h_delay, norm->h_start, norm->h_stop,
+               norm->v_delay, norm->video_v_start,
+               norm->video_v_stop);
+
+       switch (dev->vdecoder) {
+       case TW6800:
+               hdelay = norm->h_delay0;
+               break;
+       default:
+               hdelay = norm->h_delay;
+               break;
+       }
+
+       hdelay += norm->h_start;
+       hactive = norm->h_stop - norm->h_start + 1;
+
+       hscale = (hactive * 256) / (width);
+
+       vdelay = norm->v_delay;
+       vactive = ((norm->id & V4L2_STD_525_60) ? 524 : 624) / 2 - 
norm->video_v_start;
+       vscale = (vactive * 256) / height;
+
+       pr_debug("%s: %dx%d [%s%s,%s]\n", __func__,
+               width, height,
+               V4L2_FIELD_HAS_TOP(field)    ? "T" : "",
+               V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "",
+               v4l2_norm_to_name(dev->tvnorm->id));
+       pr_debug("%s: hactive=%d, hdelay=%d, hscale=%d; "
+               "vactive=%d, vdelay=%d, vscale=%d\n", __func__,
+               hactive, hdelay, hscale, vactive, vdelay, vscale);
+
+       comb =  ((vdelay & 0x300)  >> 2) |
+               ((vactive & 0x300) >> 4) |
+               ((hdelay & 0x300)  >> 6) |
+               ((hactive & 0x300) >> 8);
+       pr_debug("%s: setting CROP_HI=%02x, VDELAY_LO=%02x, "
+               "VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n",
+               __func__, comb, vdelay, vactive, hdelay, hactive);
+       tw_writeb(TW68_CROP_HI, comb);
+       tw_writeb(TW68_VDELAY_LO, vdelay & 0xff);
+       tw_writeb(TW68_VACTIVE_LO, vactive & 0xff);
+       tw_writeb(TW68_HDELAY_LO, hdelay & 0xff);
+       tw_writeb(TW68_HACTIVE_LO, hactive & 0xff);
+
+       comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8);
+       pr_debug("%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, "
+               "HSCALE_LO=%02x\n", __func__, comb, vscale, hscale);
+       tw_writeb(TW68_SCALE_HI, comb);
+       tw_writeb(TW68_VSCALE_LO, vscale);
+       tw_writeb(TW68_HSCALE_LO, hscale);
+
+       return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf)
+{
+       /* Set cropping and scaling */
+       tw68_set_scale(dev, dev->width, dev->height, dev->field);
+       /*
+        *  Set start address for RISC program.  Note that if the DMAP
+        *  processor is currently running, it must be stopped before
+        *  a new address can be set.
+        */
+       tw_clearl(TW68_DMAC, TW68_DMAP_EN);
+       tw_writel(TW68_DMAP_SA, cpu_to_le32(buf->dma));
+       /* Clear any pending interrupts */
+       tw_writel(TW68_INTSTAT, dev->board_virqmask);
+       /* Enable the risc engine and the fifo */
+       tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat |
+               ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
+       dev->pci_irqmask |= dev->board_virqmask;
+       tw_setl(TW68_INTMASK, dev->pci_irqmask);
+       return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+/* nr of (tw68-)pages for the given buffer size */
+static int tw68_buffer_pages(int size)
+{
+       size  = PAGE_ALIGN(size);
+       size += PAGE_SIZE; /* for non-page-aligned buffers */
+       size /= 4096;
+       return size;
+}
+
+/* calc max # of buffers from size (must not exceed the 4MB virtual
+ * address space per DMA channel) */
+static int tw68_buffer_count(unsigned int size, unsigned int count)
+{
+       unsigned int maxcount;
+
+       maxcount = 1024 / tw68_buffer_pages(size);
+       if (count > maxcount)
+               count = maxcount;
+       return count;
+}
+
+/* ------------------------------------------------------------- */
+/* vb2 queue operations                                          */
+
+static int tw68_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt,
+                          unsigned int *num_buffers, unsigned int *num_planes,
+                          unsigned int sizes[], void *alloc_ctxs[])
+{
+       struct tw68_dev *dev = vb2_get_drv_priv(q);
+       unsigned tot_bufs = q->num_buffers + *num_buffers;
+
+       sizes[0] = (dev->fmt->depth * dev->width * dev->height) >> 3;
+       /*
+        * We allow create_bufs, but only if the sizeimage is the same as the
+        * current sizeimage. The tw68_buffer_count calculation becomes quite
+        * difficult otherwise.
+        */
+       if (fmt && fmt->fmt.pix.sizeimage < sizes[0])
+               return -EINVAL;
+       *num_planes = 1;
+       if (tot_bufs < 2)
+               tot_bufs = 2;
+       tot_bufs = tw68_buffer_count(sizes[0], tot_bufs);
+       *num_buffers = tot_bufs - q->num_buffers;
+
+       return 0;
+}
+
+/*
+ * The risc program for each buffers works as follows: it starts with a simple
+ * 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the
+ * buffer follows and at the end we have a JUMP back to the start + 8 (skipping
+ * the initial JUMP).
+ *
+ * This is the program of the first buffer to be queued if the active list is
+ * empty and it just keeps DMAing this buffer without generating any 
interrupts.
+ *
+ * If a new buffer is added then the initial JUMP in the program generates an
+ * interrupt as well which signals that the previous buffer has been DMAed
+ * successfully and that it can be returned to userspace.
+ *
+ * It also sets the final jump of the previous buffer to the start of the new
+ * buffer, thus chaining the new buffer into the DMA chain. This is a single
+ * atomic u32 write, so there is no race condition.
+ *
+ * The end-result of all this that you only get an interrupt when a buffer
+ * is ready, so the control flow is very easy.
+ */
+static void tw68_buf_queue(struct vb2_buffer *vb)
+{
+       struct vb2_queue *vq = vb->vb2_queue;
+       struct tw68_dev *dev = vb2_get_drv_priv(vq);
+       struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+       struct tw68_buf *prev;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->slock, flags);
+
+       /* append a 'JUMP to start of buffer' to the buffer risc program */
+       buf->jmp[0] = cpu_to_le32(RISC_JUMP);
+       buf->jmp[1] = cpu_to_le32(buf->dma + 8);
+
+       if (!list_empty(&dev->active)) {
+               prev = list_entry(dev->active.prev, struct tw68_buf, list);
+               buf->cpu[0] |= cpu_to_le32(RISC_INT_BIT);
+               prev->jmp[1] = cpu_to_le32(buf->dma);
+       }
+       list_add_tail(&buf->list, &dev->active);
+       spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+/*
+ * buffer_prepare
+ *
+ * Set the ancilliary information into the buffer structure.  This
+ * includes generating the necessary risc program if it hasn't already
+ * been done for the current buffer format.
+ * The structure fh contains the details of the format requested by the
+ * user - type, width, height and #fields.  This is compared with the
+ * last format set for the current buffer.  If they differ, the risc
+ * code (which controls the filling of the buffer) is (re-)generated.
+ */
+static int tw68_buf_prepare(struct vb2_buffer *vb)
+{
+       struct vb2_queue *vq = vb->vb2_queue;
+       struct tw68_dev *dev = vb2_get_drv_priv(vq);
+       struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+       struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
+       unsigned size, bpl;
+       int rc;
+
+       size = (dev->width * dev->height * dev->fmt->depth) >> 3;
+       if (vb2_plane_size(vb, 0) < size)
+               return -EINVAL;
+       vb2_set_plane_payload(vb, 0, size);
+
+       rc = dma_map_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
+       if (!rc)
+               return -EIO;
+
+       bpl = (dev->width * dev->fmt->depth) >> 3;
+       switch (dev->field) {
+       case V4L2_FIELD_TOP:
+               tw68_risc_buffer(dev->pci, buf, dma->sgl,
+                                0, UNSET, bpl, 0, dev->height);
+               break;
+       case V4L2_FIELD_BOTTOM:
+               tw68_risc_buffer(dev->pci, buf, dma->sgl,
+                                UNSET, 0, bpl, 0, dev->height);
+               break;
+       case V4L2_FIELD_SEQ_TB:
+               tw68_risc_buffer(dev->pci, buf, dma->sgl,
+                                0, bpl * (dev->height >> 1),
+                                bpl, 0, dev->height >> 1);
+               break;
+       case V4L2_FIELD_SEQ_BT:
+               tw68_risc_buffer(dev->pci, buf, dma->sgl,
+                                bpl * (dev->height >> 1), 0,
+                                bpl, 0, dev->height >> 1);
+               break;
+       case V4L2_FIELD_INTERLACED:
+       default:
+               tw68_risc_buffer(dev->pci, buf, dma->sgl,
+                                0, bpl, bpl, bpl, dev->height >> 1);
+               break;
+       }
+       return 0;
+}
+
+static void tw68_buf_finish(struct vb2_buffer *vb)
+{
+       struct vb2_queue *vq = vb->vb2_queue;
+       struct tw68_dev *dev = vb2_get_drv_priv(vq);
+       struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
+       struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+
+       dma_unmap_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
+
+       pci_free_consistent(dev->pci, buf->size, buf->cpu, buf->dma);
+}
+
+static int tw68_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+       struct tw68_dev *dev = vb2_get_drv_priv(q);
+       struct tw68_buf *buf =
+               container_of(dev->active.next, struct tw68_buf, list);
+
+       dev->seqnr = 0;
+       tw68_video_start_dma(dev, buf);
+       return 0;
+}
+
+static void tw68_stop_streaming(struct vb2_queue *q)
+{
+       struct tw68_dev *dev = vb2_get_drv_priv(q);
+
+       /* Stop risc & fifo */
+       tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+       while (!list_empty(&dev->active)) {
+               struct tw68_buf *buf =
+                       container_of(dev->active.next, struct tw68_buf, list);
+
+               list_del(&buf->list);
+               vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+       }
+}
+
+static struct vb2_ops tw68_video_qops = {
+       .queue_setup    = tw68_queue_setup,
+       .buf_queue      = tw68_buf_queue,
+       .buf_prepare    = tw68_buf_prepare,
+       .buf_finish     = tw68_buf_finish,
+       .start_streaming = tw68_start_streaming,
+       .stop_streaming = tw68_stop_streaming,
+       .wait_prepare   = vb2_ops_wait_prepare,
+       .wait_finish    = vb2_ops_wait_finish,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int tw68_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct tw68_dev *dev =
+               container_of(ctrl->handler, struct tw68_dev, hdl);
+
+       switch (ctrl->id) {
+       case V4L2_CID_BRIGHTNESS:
+               tw_writeb(TW68_BRIGHT, ctrl->val);
+               break;
+       case V4L2_CID_HUE:
+               tw_writeb(TW68_HUE, ctrl->val);
+               break;
+       case V4L2_CID_CONTRAST:
+               tw_writeb(TW68_CONTRAST, ctrl->val);
+               break;
+       case V4L2_CID_SATURATION:
+               tw_writeb(TW68_SAT_U, ctrl->val);
+               tw_writeb(TW68_SAT_V, ctrl->val);
+               break;
+       case V4L2_CID_COLOR_KILLER:
+               if (ctrl->val)
+                       tw_andorb(TW68_MISC2, 0xe0, 0xe0);
+               else
+                       tw_andorb(TW68_MISC2, 0xe0, 0x00);
+               break;
+       case V4L2_CID_CHROMA_AGC:
+               if (ctrl->val)
+                       tw_andorb(TW68_LOOP, 0x30, 0x20);
+               else
+                       tw_andorb(TW68_LOOP, 0x30, 0x00);
+               break;
+       }
+       return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * Note that this routine returns what is stored in the fh structure, and
+ * does not interrogate any of the device registers.
+ */
+static int tw68_g_fmt_vid_cap(struct file *file, void *priv,
+                               struct v4l2_format *f)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       f->fmt.pix.width        = dev->width;
+       f->fmt.pix.height       = dev->height;
+       f->fmt.pix.field        = dev->field;
+       f->fmt.pix.pixelformat  = dev->fmt->fourcc;
+       f->fmt.pix.bytesperline =
+               (f->fmt.pix.width * (dev->fmt->depth)) >> 3;
+       f->fmt.pix.sizeimage =
+               f->fmt.pix.height * f->fmt.pix.bytesperline;
+       f->fmt.pix.colorspace   = V4L2_COLORSPACE_SMPTE170M;
+       f->fmt.pix.priv = 0;
+       return 0;
+}
+
+static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
+                                               struct v4l2_format *f)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+       const struct tw68_format *fmt;
+       enum v4l2_field field;
+       unsigned int maxh;
+
+       fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+       if (NULL == fmt)
+               return -EINVAL;
+
+       field = f->fmt.pix.field;
+       maxh  = (dev->tvnorm->id & V4L2_STD_525_60) ? 480 : 576;
+
+       switch (field) {
+       case V4L2_FIELD_TOP:
+       case V4L2_FIELD_BOTTOM:
+               break;
+       case V4L2_FIELD_INTERLACED:
+       case V4L2_FIELD_SEQ_BT:
+       case V4L2_FIELD_SEQ_TB:
+               maxh = maxh * 2;
+               break;
+       default:
+               field = (f->fmt.pix.height > maxh / 2)
+                       ? V4L2_FIELD_INTERLACED
+                       : V4L2_FIELD_BOTTOM;
+               break;
+       }
+
+       f->fmt.pix.field = field;
+       if (f->fmt.pix.width  < 48)
+               f->fmt.pix.width  = 48;
+       if (f->fmt.pix.height < 32)
+               f->fmt.pix.height = 32;
+       if (f->fmt.pix.width > 720)
+               f->fmt.pix.width = 720;
+       if (f->fmt.pix.height > maxh)
+               f->fmt.pix.height = maxh;
+       f->fmt.pix.width &= ~0x03;
+       f->fmt.pix.bytesperline =
+               (f->fmt.pix.width * (fmt->depth)) >> 3;
+       f->fmt.pix.sizeimage =
+               f->fmt.pix.height * f->fmt.pix.bytesperline;
+       f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+       return 0;
+}
+
+/*
+ * Note that tw68_s_fmt_vid_cap sets the information into the fh structure,
+ * and it will be used for all future new buffers.  However, there could be
+ * some number of buffers on the "active" chain which will be filled before
+ * the change takes place.
+ */
+static int tw68_s_fmt_vid_cap(struct file *file, void *priv,
+                                       struct v4l2_format *f)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+       int err;
+
+       err = tw68_try_fmt_vid_cap(file, priv, f);
+       if (0 != err)
+               return err;
+
+       dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+       dev->width = f->fmt.pix.width;
+       dev->height = f->fmt.pix.height;
+       dev->field = f->fmt.pix.field;
+       return 0;
+}
+
+static int tw68_enum_input(struct file *file, void *priv,
+                                       struct v4l2_input *i)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+       unsigned int n;
+
+       n = i->index;
+       if (n >= TW68_INPUT_MAX)
+               return -EINVAL;
+       i->index = n;
+       i->type = V4L2_INPUT_TYPE_CAMERA;
+       snprintf(i->name, sizeof(i->name), "Composite %d", n);
+
+       /* If the query is for the current input, get live data */
+       if (n == dev->input) {
+               int v1 = tw_readb(TW68_STATUS1);
+               int v2 = tw_readb(TW68_MVSN);
+
+               if (0 != (v1 & (1 << 7)))
+                       i->status |= V4L2_IN_ST_NO_SYNC;
+               if (0 != (v1 & (1 << 6)))
+                       i->status |= V4L2_IN_ST_NO_H_LOCK;
+               if (0 != (v1 & (1 << 2)))
+                       i->status |= V4L2_IN_ST_NO_SIGNAL;
+               if (0 != (v1 & 1 << 1))
+                       i->status |= V4L2_IN_ST_NO_COLOR;
+               if (0 != (v2 & (1 << 2)))
+                       i->status |= V4L2_IN_ST_MACROVISION;
+       }
+       i->std = video_devdata(file)->tvnorms;
+       return 0;
+}
+
+static int tw68_g_input(struct file *file, void *priv, unsigned int *i)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       *i = dev->input;
+       return 0;
+}
+
+static int tw68_s_input(struct file *file, void *priv, unsigned int i)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       if (i >= TW68_INPUT_MAX)
+               return -EINVAL;
+       dev->input = i;
+       tw_andorb(TW68_INFORM, 0x03 << 2, dev->input << 2);
+       return 0;
+}
+
+static int tw68_querycap(struct file *file, void  *priv,
+                                       struct v4l2_capability *cap)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       strcpy(cap->driver, "tw68");
+       strlcpy(cap->card, "Techwell Capture Card",
+               sizeof(cap->card));
+       sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
+       cap->device_caps =
+               V4L2_CAP_VIDEO_CAPTURE |
+               V4L2_CAP_READWRITE |
+               V4L2_CAP_STREAMING;
+
+       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+       return 0;
+}
+
+static int tw68_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+       unsigned int i;
+
+       if (vb2_is_busy(&dev->vidq))
+               return -EBUSY;
+
+       /* Look for match on complete norm id (may have mult bits) */
+       for (i = 0; i < TVNORMS; i++) {
+               if (id == tvnorms[i].id)
+                       break;
+       }
+
+       /* If no exact match, look for norm which contains this one */
+       if (i == TVNORMS) {
+               for (i = 0; i < TVNORMS; i++)
+                       if (id & tvnorms[i].id)
+                               break;
+       }
+       /* If still not matched, give up */
+       if (i == TVNORMS)
+               return -EINVAL;
+
+       set_tvnorm(dev, &tvnorms[i]);   /* do the actual setting */
+       return 0;
+}
+
+static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       *id = dev->tvnorm->id;
+       return 0;
+}
+
+static int tw68_enum_fmt_vid_cap(struct file *file, void  *priv,
+                                       struct v4l2_fmtdesc *f)
+{
+       if (f->index >= FORMATS)
+               return -EINVAL;
+
+       strlcpy(f->description, formats[f->index].name,
+               sizeof(f->description));
+
+       f->pixelformat = formats[f->index].fourcc;
+
+       return 0;
+}
+
+/*
+ * Used strictly for internal development and debugging, this routine
+ * prints out the current register contents for the tw68xx device.
+ */
+static void tw68_dump_regs(struct tw68_dev *dev)
+{
+       unsigned char line[80];
+       int i, j, k;
+       unsigned char *cptr;
+
+       pr_info("Full dump of TW68 registers:\n");
+       /* First we do the PCI regs, 8 4-byte regs per line */
+       for (i = 0; i < 0x100; i += 32) {
+               cptr = line;
+               cptr += sprintf(cptr, "%03x  ", i);
+               /* j steps through the next 4 words */
+               for (j = i; j < i + 16; j += 4)
+                       cptr += sprintf(cptr, "%08x ", tw_readl(j));
+               *cptr++ = ' ';
+               for (; j < i + 32; j += 4)
+                       cptr += sprintf(cptr, "%08x ", tw_readl(j));
+               *cptr++ = '\n';
+               *cptr = 0;
+               pr_info("%s", line);
+       }
+       /* Next the control regs, which are single-byte, address mod 4 */
+       while (i < 0x400) {
+               cptr = line;
+               cptr += sprintf(cptr, "%03x ", i);
+               /* Print out 4 groups of 4 bytes */
+               for (j = 0; j < 4; j++) {
+                       for (k = 0; k < 4; k++) {
+                               cptr += sprintf(cptr, "%02x ",
+                                       tw_readb(i));
+                               i += 4;
+                       }
+                       *cptr++ = ' ';
+               }
+               *cptr++ = '\n';
+               *cptr = 0;
+               pr_info("%s", line);
+       }
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       tw68_dump_regs(dev);
+       return v4l2_ctrl_log_status(file, priv);
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_register(struct file *file, void *priv,
+                             struct v4l2_dbg_register *reg)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       if (reg->size == 1)
+               reg->val = tw_readb(reg->reg);
+       else
+               reg->val = tw_readl(reg->reg);
+       return 0;
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+                               const struct v4l2_dbg_register *reg)
+{
+       struct tw68_dev *dev = video_drvdata(file);
+
+       if (reg->size == 1)
+               tw_writeb(reg->reg, reg->val);
+       else
+               tw_writel(reg->reg & 0xffff, reg->val);
+       return 0;
+}
+#endif
+
+static const struct v4l2_ctrl_ops tw68_ctrl_ops = {
+       .s_ctrl = tw68_s_ctrl,
+};
+
+static const struct v4l2_file_operations video_fops = {
+       .owner                  = THIS_MODULE,
+       .open                   = v4l2_fh_open,
+       .release                = vb2_fop_release,
+       .read                   = vb2_fop_read,
+       .poll                   = vb2_fop_poll,
+       .mmap                   = vb2_fop_mmap,
+       .unlocked_ioctl         = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+       .vidioc_querycap                = tw68_querycap,
+       .vidioc_enum_fmt_vid_cap        = tw68_enum_fmt_vid_cap,
+       .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
+       .vidioc_create_bufs             = vb2_ioctl_create_bufs,
+       .vidioc_querybuf                = vb2_ioctl_querybuf,
+       .vidioc_qbuf                    = vb2_ioctl_qbuf,
+       .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
+       .vidioc_s_std                   = tw68_s_std,
+       .vidioc_g_std                   = tw68_g_std,
+       .vidioc_enum_input              = tw68_enum_input,
+       .vidioc_g_input                 = tw68_g_input,
+       .vidioc_s_input                 = tw68_s_input,
+       .vidioc_streamon                = vb2_ioctl_streamon,
+       .vidioc_streamoff               = vb2_ioctl_streamoff,
+       .vidioc_g_fmt_vid_cap           = tw68_g_fmt_vid_cap,
+       .vidioc_try_fmt_vid_cap         = tw68_try_fmt_vid_cap,
+       .vidioc_s_fmt_vid_cap           = tw68_s_fmt_vid_cap,
+       .vidioc_log_status              = vidioc_log_status,
+       .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
+       .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+       .vidioc_g_register              = vidioc_g_register,
+       .vidioc_s_register              = vidioc_s_register,
+#endif
+};
+
+static struct video_device tw68_video_template = {
+       .name                   = "tw68_video",
+       .fops                   = &video_fops,
+       .ioctl_ops              = &video_ioctl_ops,
+       .release                = video_device_release_empty,
+       .tvnorms                = TW68_NORMS,
+};
+
+/* ------------------------------------------------------------------ */
+/* exported stuff                                                     */
+void tw68_set_tvnorm_hw(struct tw68_dev *dev)
+{
+       tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format);
+}
+
+int tw68_video_init1(struct tw68_dev *dev)
+{
+       struct v4l2_ctrl_handler *hdl = &dev->hdl;
+
+       v4l2_ctrl_handler_init(hdl, 6);
+       v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+                       V4L2_CID_BRIGHTNESS, -128, 127, 1, 20);
+       v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+                       V4L2_CID_CONTRAST, 0, 255, 1, 100);
+       v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+                       V4L2_CID_SATURATION, 0, 255, 1, 128);
+       /* NTSC only */
+       v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+                       V4L2_CID_HUE, -128, 127, 1, 0);
+       v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+                       V4L2_CID_COLOR_KILLER, 0, 1, 1, 0);
+       v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
+                       V4L2_CID_CHROMA_AGC, 0, 1, 1, 1);
+       if (hdl->error) {
+               v4l2_ctrl_handler_free(hdl);
+               return hdl->error;
+       }
+       dev->v4l2_dev.ctrl_handler = hdl;
+       v4l2_ctrl_handler_setup(hdl);
+       return 0;
+}
+
+int tw68_video_init2(struct tw68_dev *dev, int video_nr)
+{
+       int ret;
+
+       set_tvnorm(dev, &tvnorms[0]);
+
+       dev->fmt      = format_by_fourcc(V4L2_PIX_FMT_BGR24);
+       dev->width    = 720;
+       dev->height   = 576;
+       dev->field    = V4L2_FIELD_INTERLACED;
+
+       INIT_LIST_HEAD(&dev->active);
+       dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+       dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;
+       dev->vidq.ops = &tw68_video_qops;
+       dev->vidq.mem_ops = &vb2_dma_sg_memops;
+       dev->vidq.drv_priv = dev;
+       dev->vidq.gfp_flags = __GFP_DMA32;
+       dev->vidq.buf_struct_size = sizeof(struct tw68_buf);
+       dev->vidq.lock = &dev->lock;
+       dev->vidq.min_buffers_needed = 2;
+       ret = vb2_queue_init(&dev->vidq);
+       if (ret)
+               return ret;
+       dev->vdev = tw68_video_template;
+       dev->vdev.v4l2_dev = &dev->v4l2_dev;
+       dev->vdev.lock = &dev->lock;
+       dev->vdev.queue = &dev->vidq;
+       video_set_drvdata(&dev->vdev, dev);
+       return video_register_device(&dev->vdev, VFL_TYPE_GRABBER, video_nr);
+}
+
+/*
+ * tw68_irq_video_done
+ */
+void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status)
+{
+       __u32 reg;
+
+       /* reset interrupts handled by this routine */
+       tw_writel(TW68_INTSTAT, status);
+       /*
+        * Check most likely first
+        *
+        * DMAPI shows we have reached the end of the risc code
+        * for the current buffer.
+        */
+       if (status & TW68_DMAPI) {
+               struct tw68_buf *buf;
+
+               spin_lock(&dev->slock);
+               buf = list_entry(dev->active.next, struct tw68_buf, list);
+               list_del(&buf->list);
+               spin_unlock(&dev->slock);
+               v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
+               buf->vb.v4l2_buf.field = dev->field;
+               buf->vb.v4l2_buf.sequence = dev->seqnr++;
+               vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
+               status &= ~(TW68_DMAPI);
+               if (0 == status)
+                       return;
+       }
+       if (status & (TW68_VLOCK | TW68_HLOCK))
+               dev_dbg(&dev->pci_dev->dev, "Lost sync\n");
+       if (status & TW68_PABORT)
+               dev_err(&dev->pci_dev->dev, "PABORT interrupt\n");
+       if (status & TW68_DMAPERR)
+               dev_err(&dev->pci_dev->dev, "DMAPERR interrupt\n");
+       /*
+        * On TW6800, FDMIS is apparently generated if video input is switched
+        * during operation.  Therefore, it is not enabled for that chip.
+        */
+       if (status & TW68_FDMIS) {
+               /* logic error somewhere */
+               dev_err(&dev->pci_dev->dev, "FDMIS interrupt\n");
+       }
+       if (status & TW68_FFOF) {
+               /* probably a logic error */
+               reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN;
+               tw_clearl(TW68_DMAC, TW68_FIFO_EN);
+               dev_err(&dev->pci_dev->dev, "FFOF interrupt\n");
+               tw_setl(TW68_DMAC, reg);
+       }
+       if (status & TW68_FFERR)
+               dev_err(&dev->pci_dev->dev, "FFERR interrupt\n");
+}
diff --git a/drivers/media/pci/tw68/tw68.h b/drivers/media/pci/tw68/tw68.h
new file mode 100644
index 0000000..5541a91
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68.h
@@ -0,0 +1,233 @@
+/*
+ *  tw68 driver common header file
+ *
+ *  Much of this code is derived from the cx88 and sa7134 drivers, which
+ *  were in turn derived from the bt87x driver.  The original work was by
+ *  Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
+ *  Hans Verkuil, Andy Walls and many others.  Their work is gratefully
+ *  acknowledged.  Full credit goes to them - any problems within this code
+ *  are mine.
+ *
+ *  Copyright (C) 2009  William M. Brack <wbr...@mmm.com.hk>
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverk...@xs4all.nl>
+ *
+ *  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.
+ */
+
+#include <linux/version.h>
+#define        TW68_VERSION_CODE       KERNEL_VERSION(1, 0, 0)
+
+#include <linux/pci.h>
+#include <linux/videodev2.h>
+#include <linux/notifier.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "tw68-reg.h"
+
+#define        UNSET   (-1U)
+
+/* system vendor and device ID's */
+#define        PCI_VENDOR_ID_TECHWELL  0x1797
+#define        PCI_DEVICE_ID_6800      0x6800
+#define        PCI_DEVICE_ID_6801      0x6801
+#define        PCI_DEVICE_ID_AUDIO2    0x6802
+#define        PCI_DEVICE_ID_TS3       0x6803
+#define        PCI_DEVICE_ID_6804      0x6804
+#define        PCI_DEVICE_ID_AUDIO5    0x6805
+#define        PCI_DEVICE_ID_TS6       0x6806
+
+/* tw6816 based cards */
+#define        PCI_DEVICE_ID_6816_1   0x6810
+#define        PCI_DEVICE_ID_6816_2   0x6811
+#define        PCI_DEVICE_ID_6816_3   0x6812
+#define        PCI_DEVICE_ID_6816_4   0x6813
+
+#define TW68_NORMS ( \
+       V4L2_STD_NTSC    | V4L2_STD_PAL       | V4L2_STD_SECAM    | \
+       V4L2_STD_PAL_M   | V4L2_STD_PAL_Nc    | V4L2_STD_PAL_60)
+
+#define        TW68_VID_INTS   (TW68_FFERR | TW68_PABORT | TW68_DMAPERR | \
+                        TW68_FFOF   | TW68_DMAPI)
+/* TW6800 chips have trouble with these, so we don't set them for that chip */
+#define        TW68_VID_INTSX  (TW68_FDMIS | TW68_HLOCK | TW68_VLOCK)
+
+#define        TW68_I2C_INTS   (TW68_SBERR | TW68_SBDONE | TW68_SBERR2  | \
+                        TW68_SBDONE2)
+
+enum tw68_decoder_type {
+       TW6800,
+       TW6801,
+       TW6804,
+       TWXXXX,
+};
+
+/* ----------------------------------------------------------- */
+/* static data                                                 */
+
+struct tw68_tvnorm {
+       char            *name;
+       v4l2_std_id     id;
+
+       /* video decoder */
+       u32     sync_control;
+       u32     luma_control;
+       u32     chroma_ctrl1;
+       u32     chroma_gain;
+       u32     chroma_ctrl2;
+       u32     vgate_misc;
+
+       /* video scaler */
+       u32     h_delay;
+       u32     h_delay0;       /* for TW6800 */
+       u32     h_start;
+       u32     h_stop;
+       u32     v_delay;
+       u32     video_v_start;
+       u32     video_v_stop;
+       u32     vbi_v_start_0;
+       u32     vbi_v_stop_0;
+       u32     vbi_v_start_1;
+
+       /* Techwell specific */
+       u32     format;
+};
+
+struct tw68_format {
+       char    *name;
+       u32     fourcc;
+       u32     depth;
+       u32     twformat;
+};
+
+/* ----------------------------------------------------------- */
+/* card configuration                                    */
+
+#define TW68_BOARD_NOAUTO              UNSET
+#define TW68_BOARD_UNKNOWN             0
+#define        TW68_BOARD_GENERIC_6802         1
+
+#define        TW68_MAXBOARDS                  16
+#define        TW68_INPUT_MAX                  4
+
+/* ----------------------------------------------------------- */
+/* device / file handle status                                 */
+
+#define        BUFFER_TIMEOUT  msecs_to_jiffies(500)   /* 0.5 seconds */
+
+struct tw68_dev;       /* forward delclaration */
+
+/* buffer for one video/vbi/ts frame */
+struct tw68_buf {
+       struct vb2_buffer vb;
+       struct list_head list;
+
+       unsigned int   size;
+       __le32         *cpu;
+       __le32         *jmp;
+       dma_addr_t     dma;
+};
+
+struct tw68_fmt {
+       char                    *name;
+       u32                     fourcc; /* v4l2 format id */
+       int                     depth;
+       int                     flags;
+       u32                     twformat;
+};
+
+/* global device status */
+struct tw68_dev {
+       struct mutex            lock;
+       spinlock_t              slock;
+       u16                     instance;
+       struct v4l2_device      v4l2_dev;
+
+       /* various device info */
+       enum tw68_decoder_type  vdecoder;
+       struct video_device     vdev;
+       struct v4l2_ctrl_handler hdl;
+
+       /* pci i/o */
+       char                    *name;
+       struct pci_dev          *pci;
+       unsigned char           pci_rev, pci_lat;
+       u32                     __iomem *lmmio;
+       u8                      __iomem *bmmio;
+       u32                     pci_irqmask;
+       /* The irq mask to be used will depend upon the chip type */
+       u32                     board_virqmask;
+
+       /* video capture */
+       const struct tw68_format *fmt;
+       unsigned                width, height;
+       unsigned                seqnr;
+       unsigned                field;
+       struct vb2_queue        vidq;
+       struct list_head        active;
+
+       /* various v4l controls */
+       const struct tw68_tvnorm *tvnorm;       /* video */
+
+       int                     input;
+};
+
+/* ----------------------------------------------------------- */
+
+#define tw_readl(reg)          readl(dev->lmmio + ((reg) >> 2))
+#define        tw_readb(reg)           readb(dev->bmmio + (reg))
+#define tw_writel(reg, value)  writel((value), dev->lmmio + ((reg) >> 2))
+#define        tw_writeb(reg, value)   writeb((value), dev->bmmio + (reg))
+
+#define tw_andorl(reg, mask, value) \
+               writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\
+               ((value) & (mask)), dev->lmmio+((reg)>>2))
+#define        tw_andorb(reg, mask, value) \
+               writeb((readb(dev->bmmio + (reg)) & ~(mask)) |\
+               ((value) & (mask)), dev->bmmio+(reg))
+#define tw_setl(reg, bit)      tw_andorl((reg), (bit), (bit))
+#define        tw_setb(reg, bit)       tw_andorb((reg), (bit), (bit))
+#define        tw_clearl(reg, bit)     \
+               writel((readl(dev->lmmio + ((reg) >> 2)) & ~(bit)), \
+               dev->lmmio + ((reg) >> 2))
+#define        tw_clearb(reg, bit)     \
+               writeb((readb(dev->bmmio+(reg)) & ~(bit)), \
+               dev->bmmio + (reg))
+
+#define tw_wait(us) { udelay(us); }
+
+/* ----------------------------------------------------------- */
+/* tw68-video.c                                                */
+
+void tw68_set_tvnorm_hw(struct tw68_dev *dev);
+
+int tw68_video_init1(struct tw68_dev *dev);
+int tw68_video_init2(struct tw68_dev *dev, int video_nr);
+void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status);
+int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf);
+
+/* ----------------------------------------------------------- */
+/* tw68-risc.c                                                 */
+
+int tw68_risc_buffer(struct pci_dev *pci, struct tw68_buf *buf,
+       struct scatterlist *sglist, unsigned int top_offset,
+       unsigned int bottom_offset, unsigned int bpl,
+       unsigned int padding, unsigned int lines);
-- 
2.1.0.rc1

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

Reply via email to