linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards
@ 2014-08-26  6:33 Hans Verkuil
  2014-08-26  6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
  2014-08-26  6:33 ` [PATCHv3 2/2] MAINTAINERS: add tw68 entry Hans Verkuil
  0 siblings, 2 replies; 7+ messages in thread
From: Hans Verkuil @ 2014-08-26  6:33 UTC (permalink / raw)
  To: linux-media

Changes since v2:

- Drop William Brack's email address since that's no longer valid. I have
  not been able to contact him.
- A few dev_err calls in the interrupt handler were replaced by dev_dbg since
  those interrupts occur during normal operation, either when starting streaming
  or when switching inputs.

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.

Note that there is no audio support. If anyone is interested in adding alsa
support, please contact me. It's definitely doable.

These devices are quite common and people are using the out-of-tree driver,
so it would be nice to have proper support for this in the mainline kernel.

Regards,

        Hans


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

* [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
  2014-08-26  6:33 [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards Hans Verkuil
@ 2014-08-26  6:33 ` Hans Verkuil
  2014-09-02 19:21   ` Mauro Carvalho Chehab
  2014-08-26  6:33 ` [PATCHv3 2/2] MAINTAINERS: add tw68 entry Hans Verkuil
  1 sibling, 1 reply; 7+ messages in thread
From: Hans Verkuil @ 2014-08-26  6:33 UTC (permalink / raw)
  To: linux-media; +Cc: Hans Verkuil

From: Hans Verkuil <hans.verkuil@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.verkuil@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  |  434 ++++++++++++++
 drivers/media/pci/tw68/tw68-reg.h   |  195 +++++++
 drivers/media/pci/tw68/tw68-risc.c  |  230 ++++++++
 drivers/media/pci/tw68/tw68-video.c | 1060 +++++++++++++++++++++++++++++++++++
 drivers/media/pci/tw68/tw68.h       |  231 ++++++++
 9 files changed, 2165 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 dc2ebbe..73d9c0f 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -21,6 +21,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..baf93af
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-core.c
@@ -0,0 +1,434 @@
+/*
+ *  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
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverkuil@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");
+MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
+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, "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)",
+			dev->name, orig, tw_readl(TW68_INTSTAT));
+	dev_dbg(&dev->pci->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
+};
+
+module_pci_driver(tw68_pci_driver);
diff --git a/drivers/media/pci/tw68/tw68-reg.h b/drivers/media/pci/tw68/tw68-reg.h
new file mode 100644
index 0000000..f60b3a8
--- /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
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverkuil@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..7439db2
--- /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
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverkuil@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..66fae23
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68-video.c
@@ -0,0 +1,1060 @@
+/*
+ *  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
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverkuil@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, "Lost sync\n");
+	if (status & TW68_PABORT)
+		dev_err(&dev->pci->dev, "PABORT interrupt\n");
+	if (status & TW68_DMAPERR)
+		dev_err(&dev->pci->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)
+		dev_dbg(&dev->pci->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_dbg(&dev->pci->dev, "FFOF interrupt\n");
+		tw_setl(TW68_DMAC, reg);
+	}
+	if (status & TW68_FFERR)
+		dev_dbg(&dev->pci->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..2c8abe2
--- /dev/null
+++ b/drivers/media/pci/tw68/tw68.h
@@ -0,0 +1,231 @@
+/*
+ *  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
+ *
+ *  Refactored and updated to the latest v4l core frameworks:
+ *
+ *  Copyright (C) 2014 Hans Verkuil <hverkuil@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>
+#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


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

* [PATCHv3 2/2] MAINTAINERS: add tw68 entry
  2014-08-26  6:33 [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards Hans Verkuil
  2014-08-26  6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
@ 2014-08-26  6:33 ` Hans Verkuil
  1 sibling, 0 replies; 7+ messages in thread
From: Hans Verkuil @ 2014-08-26  6:33 UTC (permalink / raw)
  To: linux-media; +Cc: Hans Verkuil

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

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 MAINTAINERS | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index aefa948..78b38e9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9227,6 +9227,14 @@ T:	git git://linuxtv.org/media_tree.git
 S:	Odd fixes
 F:	drivers/media/usb/tm6000/
 
+TW68 VIDEO4LINUX DRIVER
+M:	Hans Verkuil <hverkuil@xs4all.nl>
+L:	linux-media@vger.kernel.org
+T:	git git://linuxtv.org/media_tree.git
+W:	http://linuxtv.org
+S:	Odd Fixes
+F:	drivers/media/pci/tw68/
+
 TPM DEVICE DRIVER
 M:	Peter Huewe <peterhuewe@gmx.de>
 M:	Ashley Lai <ashley@ashleylai.com>
-- 
2.1.0


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

* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
  2014-08-26  6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
@ 2014-09-02 19:21   ` Mauro Carvalho Chehab
  2014-09-02 19:54     ` Hans Verkuil
  0 siblings, 1 reply; 7+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-02 19:21 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: linux-media, Hans Verkuil

Em Tue, 26 Aug 2014 08:33:12 +0200
Hans Verkuil <hverkuil@xs4all.nl> escreveu:

> From: Hans Verkuil <hans.verkuil@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.verkuil@cisco.com>

I would be expecting here the  William M. Brack's SOB too.

Also, the best is to add his original work on one patch (without Kbuild
stuff) and your changes on a separate patch. That helps us to identify
what are your contributions to his code, and what was his original
copyright wording.

> ---
>  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  |  434 ++++++++++++++
>  drivers/media/pci/tw68/tw68-reg.h   |  195 +++++++
>  drivers/media/pci/tw68/tw68-risc.c  |  230 ++++++++
>  drivers/media/pci/tw68/tw68-video.c | 1060 +++++++++++++++++++++++++++++++++++
>  drivers/media/pci/tw68/tw68.h       |  231 ++++++++
>  9 files changed, 2165 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 dc2ebbe..73d9c0f 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -21,6 +21,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..baf93af
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68-core.c
> @@ -0,0 +1,434 @@
> +/*
> + *  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
> + *
> + *  Refactored and updated to the latest v4l core frameworks:
> + *
> + *  Copyright (C) 2014 Hans Verkuil <hverkuil@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");
> +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
> +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, "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)",
> +			dev->name, orig, tw_readl(TW68_INTSTAT));
> +	dev_dbg(&dev->pci->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
> +};
> +
> +module_pci_driver(tw68_pci_driver);
> diff --git a/drivers/media/pci/tw68/tw68-reg.h b/drivers/media/pci/tw68/tw68-reg.h
> new file mode 100644
> index 0000000..f60b3a8
> --- /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
> + *
> + *  Refactored and updated to the latest v4l core frameworks:
> + *
> + *  Copyright (C) 2014 Hans Verkuil <hverkuil@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..7439db2
> --- /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
> + *
> + *  Refactored and updated to the latest v4l core frameworks:
> + *
> + *  Copyright (C) 2014 Hans Verkuil <hverkuil@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..66fae23
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68-video.c
> @@ -0,0 +1,1060 @@
> +/*
> + *  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
> + *
> + *  Refactored and updated to the latest v4l core frameworks:
> + *
> + *  Copyright (C) 2014 Hans Verkuil <hverkuil@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;

The above seems to be wrong, as PAGE_SIZE is not always 4096.

IMHO, the correct would be to do size /= PAGE_SIZE, if the intent is
to return the number of pages.

> +	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);

Again, the 1024 here looks weird, as it seems to also be assuming a
4096 page 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);

Please put the above statement into a single line.

> +
> +	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);

Same here. Or if you want to use multiple lines, do it like:

		struct tw68_buf *buf;

		buf = container_of(dev->active.next, struct tw68_buf, list);

(personally, I prefer this way)

Same on other similar places.

> +
> +		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, "Lost sync\n");
> +	if (status & TW68_PABORT)
> +		dev_err(&dev->pci->dev, "PABORT interrupt\n");
> +	if (status & TW68_DMAPERR)
> +		dev_err(&dev->pci->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)
> +		dev_dbg(&dev->pci->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_dbg(&dev->pci->dev, "FFOF interrupt\n");
> +		tw_setl(TW68_DMAC, reg);
> +	}
> +	if (status & TW68_FFERR)
> +		dev_dbg(&dev->pci->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..2c8abe2
> --- /dev/null
> +++ b/drivers/media/pci/tw68/tw68.h
> @@ -0,0 +1,231 @@
> +/*
> + *  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
> + *
> + *  Refactored and updated to the latest v4l core frameworks:
> + *
> + *  Copyright (C) 2014 Hans Verkuil <hverkuil@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>
> +#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);

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

* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
  2014-09-02 19:21   ` Mauro Carvalho Chehab
@ 2014-09-02 19:54     ` Hans Verkuil
  2014-09-02 20:23       ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 7+ messages in thread
From: Hans Verkuil @ 2014-09-02 19:54 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: linux-media, Hans Verkuil

On 09/02/2014 09:21 PM, Mauro Carvalho Chehab wrote:
> Em Tue, 26 Aug 2014 08:33:12 +0200
> Hans Verkuil <hverkuil@xs4all.nl> escreveu:
> 
>> From: Hans Verkuil <hans.verkuil@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.verkuil@cisco.com>
> 
> I would be expecting here the  William M. Brack's SOB too.

Sorry, that's not possible. The only email I have no longer works. I googled
to see if I could find another email without success. I met him years
ago during an ELC and he was a retired engineer, so he may not even be
around anymore. His gitorious.org repo has been inactive for over two years.

> 
> Also, the best is to add his original work on one patch (without Kbuild
> stuff) and your changes on a separate patch. That helps us to identify
> what are your contributions to his code, and what was his original
> copyright wording.

Are you sure you want that in the mainline kernel? His code is in gitorious,
a link to that is in the commit log. I'm not too keen to add code to the
kernel which is promptly being overwritten by another version. An alternative
might be to add a link to that repo to tw68-core.c as well so that it is
not just in the commit log but also in the source code.

Regards,

	Hans


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

* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
  2014-09-02 19:54     ` Hans Verkuil
@ 2014-09-02 20:23       ` Mauro Carvalho Chehab
  2014-09-02 22:04         ` Hans Verkuil
  0 siblings, 1 reply; 7+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-02 20:23 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: linux-media, Hans Verkuil

Em Tue, 02 Sep 2014 21:54:24 +0200
Hans Verkuil <hverkuil@xs4all.nl> escreveu:

> On 09/02/2014 09:21 PM, Mauro Carvalho Chehab wrote:
> > Em Tue, 26 Aug 2014 08:33:12 +0200
> > Hans Verkuil <hverkuil@xs4all.nl> escreveu:
> > 
> >> From: Hans Verkuil <hans.verkuil@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.verkuil@cisco.com>
> > 
> > I would be expecting here the  William M. Brack's SOB too.
> 
> Sorry, that's not possible. The only email I have no longer works. I googled
> to see if I could find another email without success. I met him years
> ago during an ELC and he was a retired engineer, so he may not even be
> around anymore. His gitorious.org repo has been inactive for over two years.
> 
> > 
> > Also, the best is to add his original work on one patch (without Kbuild
> > stuff) and your changes on a separate patch. That helps us to identify
> > what are your contributions to his code, and what was his original
> > copyright wording.
> 
> Are you sure you want that in the mainline kernel? His code is in gitorious,
> a link to that is in the commit log. I'm not too keen to add code to the
> kernel which is promptly being overwritten by another version. An alternative
> might be to add a link to that repo to tw68-core.c as well so that it is
> not just in the commit log but also in the source code.

There's no way to warrant that his gitorious repository will stay there
forever. We need to have the reference code somewhere, if something ever
complain about copyrights, especially since you're not able to contact
the author of the driver.

> 
> Regards,
> 
> 	Hans
> 

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

* Re: [PATCHv3 1/2] tw68: add support for Techwell tw68xx PCI grabber boards
  2014-09-02 20:23       ` Mauro Carvalho Chehab
@ 2014-09-02 22:04         ` Hans Verkuil
  0 siblings, 0 replies; 7+ messages in thread
From: Hans Verkuil @ 2014-09-02 22:04 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: linux-media, Hans Verkuil

On 09/02/2014 10:23 PM, Mauro Carvalho Chehab wrote:
> Em Tue, 02 Sep 2014 21:54:24 +0200
> Hans Verkuil <hverkuil@xs4all.nl> escreveu:
> 
>> On 09/02/2014 09:21 PM, Mauro Carvalho Chehab wrote:
>>> Em Tue, 26 Aug 2014 08:33:12 +0200
>>> Hans Verkuil <hverkuil@xs4all.nl> escreveu:
>>>
>>>> From: Hans Verkuil <hans.verkuil@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.verkuil@cisco.com>
>>>
>>> I would be expecting here the  William M. Brack's SOB too.
>>
>> Sorry, that's not possible. The only email I have no longer works. I googled
>> to see if I could find another email without success. I met him years
>> ago during an ELC and he was a retired engineer, so he may not even be
>> around anymore. His gitorious.org repo has been inactive for over two years.
>>
>>>
>>> Also, the best is to add his original work on one patch (without Kbuild
>>> stuff) and your changes on a separate patch. That helps us to identify
>>> what are your contributions to his code, and what was his original
>>> copyright wording.
>>
>> Are you sure you want that in the mainline kernel? His code is in gitorious,
>> a link to that is in the commit log. I'm not too keen to add code to the
>> kernel which is promptly being overwritten by another version. An alternative
>> might be to add a link to that repo to tw68-core.c as well so that it is
>> not just in the commit log but also in the source code.
> 
> There's no way to warrant that his gitorious repository will stay there
> forever. We need to have the reference code somewhere, if something ever
> complain about copyrights, especially since you're not able to contact
> the author of the driver.

OK, I'll prepare a new pull request tomorrow.

Regards,

	Hans


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

end of thread, other threads:[~2014-09-02 22:04 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-08-26  6:33 [PATCHv3 0/2] Add driver for tw68xx PCI grabber boards Hans Verkuil
2014-08-26  6:33 ` [PATCHv3 1/2] tw68: add support for Techwell " Hans Verkuil
2014-09-02 19:21   ` Mauro Carvalho Chehab
2014-09-02 19:54     ` Hans Verkuil
2014-09-02 20:23       ` Mauro Carvalho Chehab
2014-09-02 22:04         ` Hans Verkuil
2014-08-26  6:33 ` [PATCHv3 2/2] MAINTAINERS: add tw68 entry Hans Verkuil

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).