Pārlūkot izejas kodu

[media] tw68: add original tw68 code

This tw68 driver has been out-of-tree for many years on gitorious:
https://gitorious.org/tw68/tw68-v2.

This copies that code to the kernel as a record of that original code.

Note that William Brack's email address in these sources is no longer
valid and I have not been able to contact him. However, all the code is
standard GPL.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
Hans Verkuil 11 gadi atpakaļ
vecāks
revīzija
5740f4e75f

+ 172 - 0
drivers/media/pci/tw68/tw68-cards.c

@@ -0,0 +1,172 @@
+/*
+ *  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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>		/* must appear before i2c-algo-bit.h */
+#include <linux/i2c-algo-bit.h>
+
+#include <media/v4l2-common.h>
+#include <media/tveeprom.h>
+
+#include "tw68.h"
+#include "tw68-reg.h"
+
+/* commly used strings */
+#if 0
+static char name_mute[]    = "mute";
+static char name_radio[]   = "Radio";
+static char name_tv[]      = "Television";
+static char name_tv_mono[] = "TV (mono only)";
+static char name_svideo[]  = "S-Video";
+static char name_comp[]    = "Composite";
+#endif
+static char name_comp1[]   = "Composite1";
+static char name_comp2[]   = "Composite2";
+static char name_comp3[]   = "Composite3";
+static char name_comp4[]   = "Composite4";
+
+/* ------------------------------------------------------------------ */
+/* board config info                                                  */
+
+/* If radio_type !=UNSET, radio_addr should be specified
+ */
+
+struct tw68_board tw68_boards[] = {
+	[TW68_BOARD_UNKNOWN] = {
+		.name		= "GENERIC",
+		.tuner_type	= TUNER_ABSENT,
+		.radio_type     = UNSET,
+		.tuner_addr	= ADDR_UNSET,
+		.radio_addr	= ADDR_UNSET,
+
+		.inputs         = {
+			{
+				.name = name_comp1,
+				.vmux = 0,
+			}, {
+				.name = name_comp2,
+				.vmux = 1,
+			}, {
+				.name = name_comp3,
+				.vmux = 2,
+			}, {
+				.name = name_comp4,
+				.vmux = 3,
+			}, {	/* Must have a NULL entry at end of list */
+				.name = NULL,
+				.vmux = 0,
+			}
+		},
+	},
+};
+
+const unsigned int tw68_bcount = ARRAY_SIZE(tw68_boards);
+
+/*
+ * 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[] = {
+	{
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6800,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6801,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6804,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6816_1,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6816_2,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6816_3,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		.vendor		= PCI_VENDOR_ID_TECHWELL,
+		.device		= PCI_DEVICE_ID_6816_4,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= TW68_BOARD_UNKNOWN,
+	}, {
+		/* end of list */
+	}
+};
+MODULE_DEVICE_TABLE(pci, tw68_pci_tbl);
+
+/* ------------------------------------------------------------ */
+/* stuff done before i2c enabled */
+int tw68_board_init1(struct tw68_dev *dev)
+{
+	/* Clear GPIO outputs */
+	tw_writel(TW68_GPOE, 0);
+	/* Remainder of setup according to board ID */
+	switch (dev->board) {
+	case TW68_BOARD_UNKNOWN:
+		printk(KERN_INFO "%s: Unable to determine board type, "
+				"using generic values\n", dev->name);
+		break;
+	}
+	dev->input = dev->hw_input = &card_in(dev,0);
+	return 0;
+}
+
+int tw68_tuner_setup(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+/* stuff which needs working i2c */
+int tw68_board_init2(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+

+ 1091 - 0
drivers/media/pci/tw68/tw68-core.c

@@ -0,0 +1,1091 @@
+/*
+ *  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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/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 <wbrack@mmm.com.hk>");
+MODULE_LICENSE("GPL");
+
+static unsigned int core_debug;
+module_param(core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core]");
+
+static unsigned int gpio_tracking;
+module_param(gpio_tracking, int, 0644);
+MODULE_PARM_DESC(gpio_tracking, "enable debug messages [gpio]");
+
+static unsigned int alsa = 1;
+module_param(alsa, int, 0644);
+MODULE_PARM_DESC(alsa, "enable/disable ALSA DMA sound [dmasound]");
+
+static unsigned int latency = UNSET;
+module_param(latency, int, 0444);
+MODULE_PARM_DESC(latency, "pci latency timer");
+
+static unsigned int nocomb;
+module_param(nocomb, int, 0644);
+MODULE_PARM_DESC(nocomb, "disable comb filter");
+
+static unsigned int video_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+static unsigned int vbi_nr[]   = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+static unsigned int radio_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+static unsigned int tuner[]    = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+static unsigned int card[]     = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
+
+module_param_array(video_nr, int, NULL, 0444);
+module_param_array(vbi_nr,   int, NULL, 0444);
+module_param_array(radio_nr, int, NULL, 0444);
+module_param_array(tuner,    int, NULL, 0444);
+module_param_array(card,     int, NULL, 0444);
+
+MODULE_PARM_DESC(video_nr, "video device number");
+MODULE_PARM_DESC(vbi_nr,   "vbi device number");
+MODULE_PARM_DESC(radio_nr, "radio device number");
+MODULE_PARM_DESC(tuner,    "tuner type");
+MODULE_PARM_DESC(card,     "card type");
+
+LIST_HEAD(tw68_devlist);
+EXPORT_SYMBOL(tw68_devlist);
+DEFINE_MUTEX(tw68_devlist_lock);
+EXPORT_SYMBOL(tw68_devlist_lock);
+static LIST_HEAD(mops_list);
+static unsigned int tw68_devcount;      /* curr tot num of devices present */
+
+int (*tw68_dmasound_init)(struct tw68_dev *dev);
+EXPORT_SYMBOL(tw68_dmasound_init);
+int (*tw68_dmasound_exit)(struct tw68_dev *dev);
+EXPORT_SYMBOL(tw68_dmasound_exit);
+
+#define dprintk(level, fmt, arg...)      if (core_debug & (level)) \
+	printk(KERN_DEBUG "%s: " fmt, dev->name , ## arg)
+
+/* ------------------------------------------------------------------ */
+
+void tw68_dma_free(struct videobuf_queue *q, struct tw68_buf *buf)
+{
+	struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb);
+	
+	if (core_debug & DBG_FLOW)
+		printk(KERN_DEBUG "%s: called\n", __func__);
+	BUG_ON(in_interrupt());
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,36)
+	videobuf_waiton(&buf->vb, 0, 0);
+#else
+	videobuf_waiton(q, &buf->vb, 0, 0);
+#endif
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,35)	
+	videobuf_dma_unmap(q, dma);
+#else
+	videobuf_dma_unmap(q->dev, dma);
+#endif
+	videobuf_dma_free(dma);
+	/* if no risc area allocated, btcx_riscmem_free just returns */
+	btcx_riscmem_free(to_pci_dev(q->dev), &buf->risc);
+	buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+/* ------------------------------------------------------------------ */
+/* ------------- placeholders for later development ----------------- */
+
+static int tw68_input_init1(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+static void tw68_input_fini(struct tw68_dev *dev)
+{
+	return;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
+static void tw68_ir_start(struct tw68_dev *dev, struct card_ir *ir)
+{
+	return;
+}
+
+static void tw68_ir_stop(struct tw68_dev *dev)
+{
+	return;
+}
+#endif
+
+/* ------------------------------------------------------------------ */
+/*
+ * Buffer handling routines
+ *
+ * These routines are "generic", i.e. are intended to be used by more
+ * than one module, e.g. the video and the transport stream modules.
+ * To accomplish this generality, callbacks are used whenever some
+ * module-specific test or action is required.
+ */
+
+/* resends a current buffer in queue after resume */
+int tw68_buffer_requeue(struct tw68_dev *dev,
+				  struct tw68_dmaqueue *q)
+{
+	struct tw68_buf *buf, *prev;
+
+	dprintk(DBG_FLOW | DBG_TESTING, "%s: called\n", __func__);
+	if (!list_empty(&q->active)) {
+		buf = list_entry(q->active.next, struct tw68_buf, vb.queue);
+		dprintk(DBG_BUFF, "%s: [%p/%d] restart dma\n", __func__,
+			buf, buf->vb.i);
+		q->start_dma(dev, q, buf);
+		mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT);
+		return 0;
+	}
+
+	prev = NULL;
+	for (;;) {
+		if (list_empty(&q->queued))
+			return 0;
+		buf = list_entry(q->queued.next, struct tw68_buf, vb.queue);
+		/* if nothing precedes this one */
+		if (NULL == prev) {
+			list_move_tail(&buf->vb.queue, &q->active);
+			q->start_dma(dev, q, buf);
+			buf->activate(dev, buf, NULL);
+			dprintk(DBG_BUFF, "%s: [%p/%d] first active\n",
+				__func__, buf, buf->vb.i);
+
+		} else if (q->buf_compat(prev, buf) &&
+			   (prev->fmt == buf->fmt)) {
+			list_move_tail(&buf->vb.queue, &q->active);
+			buf->activate(dev, buf, NULL);
+			prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+			dprintk(DBG_BUFF, "%s: [%p/%d] move to active\n",
+				__func__, buf, buf->vb.i);
+		} else {
+			dprintk(DBG_BUFF, "%s: no action taken\n", __func__);
+			return 0;
+		}
+		prev = buf;
+	}
+}
+
+/* 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) */
+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;
+}
+
+/*
+ * tw68_wakeup
+ *
+ * Called when the driver completes filling a buffer, and tasks waiting
+ * for the data need to be awakened.
+ */
+void tw68_wakeup(struct tw68_dmaqueue *q, unsigned int *fc)
+{
+	struct tw68_dev *dev = q->dev;
+	struct tw68_buf *buf;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	if (list_empty(&q->active)) {
+		dprintk(DBG_BUFF | DBG_TESTING, "%s: active list empty",
+			__func__);
+		del_timer(&q->timeout);
+		return;
+	}
+	buf = list_entry(q->active.next, struct tw68_buf, vb.queue);
+	do_gettimeofday(&buf->vb.ts);
+	buf->vb.field_count = (*fc)++;
+	dprintk(DBG_BUFF | DBG_TESTING, "%s: [%p/%d] field_count=%d\n",
+		__func__, buf, buf->vb.i, *fc);
+	buf->vb.state = VIDEOBUF_DONE;
+	list_del(&buf->vb.queue);
+	wake_up(&buf->vb.done);
+	mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT);
+}
+
+/*
+ * tw68_buffer_queue
+ *
+ * Add specified buffer to specified queue
+ */
+void tw68_buffer_queue(struct tw68_dev *dev,
+			 struct tw68_dmaqueue *q,
+			 struct tw68_buf *buf)
+{
+	struct tw68_buf    *prev;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	assert_spin_locked(&dev->slock);
+	dprintk(DBG_BUFF, "%s: queuing buffer %p\n", __func__, buf);
+
+	/* append a 'JUMP to stopper' to the buffer risc program */
+	buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_INT_BIT);
+	buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma);
+
+	/* if this buffer is not "compatible" (in dimensions and format)
+	 * with the currently active chain of buffers, we must change
+	 * settings before filling it; if a previous buffer has already
+	 * been determined to require changes, this buffer must follow
+	 * it.  To do this, we maintain a "queued" chain.  If that
+	 * chain exists, append this buffer to it */
+	if (!list_empty(&q->queued)) {
+		list_add_tail(&buf->vb.queue, &q->queued);
+		buf->vb.state = VIDEOBUF_QUEUED;
+		dprintk(DBG_BUFF, "%s: [%p/%d] appended to queued\n",
+			__func__, buf, buf->vb.i);
+
+	/* else if the 'active' chain doesn't yet exist we create it now */
+	} else if (list_empty(&q->active)) {
+		dprintk(DBG_BUFF, "%s: [%p/%d] first active\n",
+			__func__, buf, buf->vb.i);
+		list_add_tail(&buf->vb.queue, &q->active);
+		q->start_dma(dev, q, buf);	/* 1st one - start dma */
+		/* TODO - why have we removed buf->count and q->count? */
+		buf->activate(dev, buf, NULL);
+
+	/* else we would like to put this buffer on the tail of the
+	 * active chain, provided it is "compatible". */
+	} else {
+		/* "compatibility" depends upon the type of buffer */
+		prev = list_entry(q->active.prev, struct tw68_buf, vb.queue);
+		if (q->buf_compat(prev, buf)) {
+			/* If "compatible", append to active chain */
+			prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+			/* the param 'prev' is only for debug printing */
+			buf->activate(dev, buf, prev);
+			list_add_tail(&buf->vb.queue, &q->active);
+			dprintk(DBG_BUFF, "%s: [%p/%d] appended to active\n",
+				__func__, buf, buf->vb.i);
+		} else {
+			/* If "incompatible", append to queued chain */
+			list_add_tail(&buf->vb.queue, &q->queued);
+			buf->vb.state = VIDEOBUF_QUEUED;
+			dprintk(DBG_BUFF, "%s: [%p/%d] incompatible - appended "
+				"to queued\n", __func__, buf, buf->vb.i);
+		}
+	}
+}
+
+/*
+ * tw68_buffer_timeout
+ *
+ * This routine is set as the video_q.timeout.function
+ *
+ * Log the event, try to reset the h/w.
+ * Flag the current buffer as failed, try to start again with next buff
+ */
+void tw68_buffer_timeout(unsigned long data)
+{
+	struct tw68_dmaqueue *q = (struct tw68_dmaqueue *)data;
+	struct tw68_dev *dev = q->dev;
+	struct tw68_buf *buf;
+	unsigned long flags;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	spin_lock_irqsave(&dev->slock, flags);
+
+	/* flag all current active buffers as failed */
+	while (!list_empty(&q->active)) {
+		buf = list_entry(q->active.next, struct tw68_buf, vb.queue);
+		list_del(&buf->vb.queue);
+		buf->vb.state = VIDEOBUF_ERROR;
+		wake_up(&buf->vb.done);
+		printk(KERN_INFO "%s/0: [%p/%d] timeout - dma=0x%08lx\n",
+			dev->name, buf, buf->vb.i,
+			(unsigned long)buf->risc.dma);
+	}
+	tw68_buffer_requeue(dev, q);
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+/* ------------------------------------------------------------------ */
+/* early init (no i2c, no irq) */
+
+/* Called from tw68_hw_init1 and tw68_resume */
+static int tw68_hw_enable1(struct tw68_dev *dev)
+{
+	return 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)
+{
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	/* 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);
+	tw68_vbi_init1(dev);
+	if (card_has_mpeg(dev))
+		tw68_ts_init1(dev);
+	tw68_input_init1(dev);
+
+	/* Do any other h/w early initialisation at this point */
+	tw68_hw_enable1(dev);
+
+	return 0;
+}
+
+/* late init (with i2c + irq) */
+static int tw68_hw_enable2(struct tw68_dev *dev)
+{
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+#ifdef	TW68_TESTING
+	dev->pci_irqmask |= TW68_I2C_INTS;
+#endif
+	tw_setl(TW68_INTMASK, dev->pci_irqmask);
+	return 0;
+}
+
+static int tw68_hw_init2(struct tw68_dev *dev)
+{
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	tw68_video_init2(dev);	/* initialise video function first */
+	tw68_tvaudio_init2(dev);/* audio next */
+
+	/* all other board-related things, incl. enabling interrupts */
+	tw68_hw_enable2(dev);
+	return 0;
+}
+
+/* shutdown */
+static int tw68_hwfini(struct tw68_dev *dev)
+{
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	if (card_has_mpeg(dev))
+		tw68_ts_fini(dev);
+	tw68_input_fini(dev);
+	tw68_vbi_fini(dev);
+	tw68_tvaudio_fini(dev);
+	return 0;
+}
+
+static void __devinit must_configure_manually(void)
+{
+	unsigned int i, p;
+
+	printk(KERN_WARNING
+	       "tw68: <rant>\n"
+	       "tw68:  Congratulations!  Your TV card vendor saved a few\n"
+	       "tw68:  cents for a eeprom, thus your pci board has no\n"
+	       "tw68:  subsystem ID and I can't identify it automatically\n"
+	       "tw68: </rant>\n"
+	       "tw68: I feel better now.  Ok, here is the good news:\n"
+	       "tw68: You can use the card=<nr> insmod option to specify\n"
+	       "tw68: which board you have.  The list:\n");
+	for (i = 0; i < tw68_bcount; i++) {
+		printk(KERN_WARNING "tw68:   card=%d -> %-40.40s",
+		       i, tw68_boards[i].name);
+		for (p = 0; tw68_pci_tbl[p].driver_data; p++) {
+			if (tw68_pci_tbl[p].driver_data != i)
+				continue;
+			printk(" %04x:%04x",
+			       tw68_pci_tbl[p].subvendor,
+			       tw68_pci_tbl[p].subdevice);
+		}
+		printk("\n");
+	}
+}
+
+
+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_RETVAL(0);	/* Nope - return */
+	for (loop = 0; loop < 10; loop++) {
+		if (status & dev->board_virqmask)	/* video interrupt */
+			tw68_irq_video_done(dev, status);
+#ifdef TW68_TESTING
+		if (status & TW68_I2C_INTS)
+			tw68_irq_i2c(dev, status);
+#endif
+		status = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
+		if (0 == status)
+			goto out;
+	}
+	dprintk(DBG_UNEXPECTED, "%s: **** INTERRUPT NOT HANDLED - clearing mask"
+				" (orig 0x%08x, cur 0x%08x)",
+				dev->name, orig, tw_readl(TW68_INTSTAT));
+	dprintk(DBG_UNEXPECTED, "%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);
+out:
+	return IRQ_RETVAL(1);
+}
+
+int tw68_set_dmabits(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+static struct video_device *vdev_init(struct tw68_dev *dev,
+				      struct video_device *template,
+				      char *type)
+{
+	struct video_device *vfd;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	vfd = video_device_alloc();
+	if (NULL == vfd)
+		return NULL;
+	*vfd = *template;
+	vfd->minor   = -1;
+	vfd->parent  = &dev->pci->dev;
+	vfd->release = video_device_release;
+	/* vfd->debug   = tw_video_debug; */
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)",
+		 dev->name, type, tw68_boards[dev->board].name);
+	return vfd;
+}
+
+static void tw68_unregister_video(struct tw68_dev *dev)
+{
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	if (dev->video_dev) {
+		if (-1 != dev->video_dev->minor)
+			video_unregister_device(dev->video_dev);
+		else
+			video_device_release(dev->video_dev);
+		dev->video_dev = NULL;
+	}
+	if (dev->vbi_dev) {
+		if (-1 != dev->vbi_dev->minor)
+			video_unregister_device(dev->vbi_dev);
+		else
+			video_device_release(dev->vbi_dev);
+		dev->vbi_dev = NULL;
+	}
+	if (dev->radio_dev) {
+		if (-1 != dev->radio_dev->minor)
+			video_unregister_device(dev->radio_dev);
+		else
+			video_device_release(dev->radio_dev);
+		dev->radio_dev = NULL;
+	}
+}
+
+static void mpeg_ops_attach(struct tw68_mpeg_ops *ops,
+			    struct tw68_dev *dev)
+{
+	int err;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	if (NULL != dev->mops)
+		return;
+	if (tw68_boards[dev->board].mpeg != ops->type)
+		return;
+	err = ops->init(dev);
+	if (0 != err)
+		return;
+	dev->mops = ops;
+}
+
+static void mpeg_ops_detach(struct tw68_mpeg_ops *ops,
+			    struct tw68_dev *dev)
+{
+
+	if (NULL == dev->mops)
+		return;
+	if (dev->mops != ops)
+		return;
+	dev->mops->fini(dev);
+	dev->mops = NULL;
+}
+
+static int __devinit tw68_initdev(struct pci_dev *pci_dev,
+				     const struct pci_device_id *pci_id)
+{
+	struct tw68_dev *dev;
+	struct tw68_mpeg_ops *mops;
+	int err;
+
+	if (tw68_devcount == TW68_MAXBOARDS)
+		return -ENOMEM;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (NULL == dev)
+		return -ENOMEM;
+
+	err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
+	if (err)
+		goto fail0;
+
+	/* pci init */
+	dev->pci = pci_dev;
+	if (pci_enable_device(pci_dev)) {
+		err = -EIO;
+		goto fail1;
+	}
+
+	dev->nr = tw68_devcount;
+	sprintf(dev->name, "tw%x[%d]", pci_dev->device, dev->nr);
+
+	/* pci quirks */
+	if (pci_pci_problems) {
+		if (pci_pci_problems & PCIPCI_TRITON)
+			printk(KERN_INFO "%s: quirk: PCIPCI_TRITON\n",
+				dev->name);
+		if (pci_pci_problems & PCIPCI_NATOMA)
+			printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA\n",
+				dev->name);
+		if (pci_pci_problems & PCIPCI_VIAETBF)
+			printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF\n",
+				dev->name);
+		if (pci_pci_problems & PCIPCI_VSFX)
+			printk(KERN_INFO "%s: quirk: PCIPCI_VSFX\n",
+				dev->name);
+#ifdef PCIPCI_ALIMAGIK
+		if (pci_pci_problems & PCIPCI_ALIMAGIK) {
+			printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK "
+				"-- latency fixup\n", dev->name);
+			latency = 0x0A;
+		}
+#endif
+	}
+	if (UNSET != latency) {
+		printk(KERN_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);
+	printk(KERN_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,
+	       (unsigned long long)pci_resource_start(pci_dev, 0));
+	pci_set_master(pci_dev);
+	if (!pci_dma_supported(pci_dev, DMA_BIT_MASK(32))) {
+		printk("%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 TW6805 */
+		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;
+	}
+	/* board config */
+	dev->board = pci_id->driver_data;
+	if (card[dev->nr] >= 0 &&
+	    card[dev->nr] < tw68_bcount)
+		dev->board = card[dev->nr];
+	if (TW68_BOARD_NOAUTO == dev->board) {
+		must_configure_manually();
+		dev->board = TW68_BOARD_UNKNOWN;
+	}
+	dev->autodetected = card[dev->nr] != dev->board;
+	dev->tuner_type = tw68_boards[dev->board].tuner_type;
+	dev->tuner_addr = tw68_boards[dev->board].tuner_addr;
+	dev->radio_type = tw68_boards[dev->board].radio_type;
+	dev->radio_addr = tw68_boards[dev->board].radio_addr;
+	dev->tda9887_conf = tw68_boards[dev->board].tda9887_conf;
+	if (UNSET != tuner[dev->nr])
+		dev->tuner_type = tuner[dev->nr];
+	printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n",
+		dev->name, pci_dev->subsystem_vendor,
+		pci_dev->subsystem_device, tw68_boards[dev->board].name,
+		dev->board, dev->autodetected ?
+		"autodetected" : "insmod option");
+
+	/* get mmio */
+	if (!request_mem_region(pci_resource_start(pci_dev, 0),
+				pci_resource_len(pci_dev, 0),
+				dev->name)) {
+		err = -EBUSY;
+		printk(KERN_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;
+		printk(KERN_ERR "%s: can't ioremap() MMIO memory\n",
+		       dev->name);
+		goto fail2;
+	}
+	/* initialize hardware #1 */
+	/* First, take care of anything unique to a particular card */
+	tw68_board_init1(dev);
+	/* Then do any initialisation wanted before interrupts are on */
+	tw68_hw_init1(dev);
+
+	/* get irq */
+	err = request_irq(pci_dev->irq, tw68_irq,
+			  IRQF_SHARED | IRQF_DISABLED, dev->name, dev);
+	if (err < 0) {
+		printk(KERN_ERR "%s: can't get IRQ %d\n",
+		       dev->name, pci_dev->irq);
+		goto fail3;
+	}
+
+#ifdef TW68_TESTING
+	dev->pci_irqmask |= TW68_SBDONE;
+	tw_setl(TW68_INTMASK, dev->pci_irqmask);
+	printk(KERN_INFO "Calling tw68_i2c_register\n");
+	/* Register the i2c bus */
+	tw68_i2c_register(dev);
+#endif
+
+	/*
+	 *  Now do remainder of initialisation, first for
+	 *  things unique for this card, then for general board
+	 */
+	tw68_board_init2(dev);
+
+	tw68_hw_init2(dev);
+
+#if 0
+	/* load i2c helpers */
+	if (card_is_empress(dev)) {
+		struct v4l2_subdev *sd =
+			v4l2_i2c_new_subdev(&dev->i2c_adap, "saa6752hs",
+				"saa6752hs", 0x20);
+
+		if (sd)
+			sd->grp_id = GRP_EMPRESS;
+	}
+
+	request_submodules(dev);
+#endif
+
+	v4l2_prio_init(&dev->prio);
+
+	mutex_lock(&tw68_devlist_lock);
+	list_for_each_entry(mops, &mops_list, next)
+		mpeg_ops_attach(mops, dev);
+	list_add_tail(&dev->devlist, &tw68_devlist);
+	mutex_unlock(&tw68_devlist_lock);
+
+	/* check for signal */
+	tw68_irq_video_signalchange(dev);
+
+#if 0
+	if (TUNER_ABSENT != dev->tuner_type)
+		tw_call_all(dev, core, s_standby, 0);
+#endif
+
+	dev->video_dev = vdev_init(dev, &tw68_video_template, "video");
+	err = video_register_device(dev->video_dev, VFL_TYPE_GRABBER,
+				    video_nr[dev->nr]);
+	if (err < 0) {
+		printk(KERN_INFO "%s: can't register video device\n",
+		       dev->name);
+		goto fail4;
+	}
+	printk(KERN_INFO "%s: registered device video%d [v4l2]\n",
+	       dev->name, dev->video_dev->num);
+
+	dev->vbi_dev = vdev_init(dev, &tw68_video_template, "vbi");
+
+	err = video_register_device(dev->vbi_dev, VFL_TYPE_VBI,
+				    vbi_nr[dev->nr]);
+	if (err < 0) {
+		printk(KERN_INFO "%s: can't register vbi device\n",
+			dev->name);
+		goto fail4;
+	}
+	printk(KERN_INFO "%s: registered device vbi%d\n",
+	       dev->name, dev->vbi_dev->num);
+
+	if (card_has_radio(dev)) {
+		dev->radio_dev = vdev_init(dev, &tw68_radio_template,
+					   "radio");
+		err = video_register_device(dev->radio_dev, VFL_TYPE_RADIO,
+					    radio_nr[dev->nr]);
+		if (err < 0) {
+			/* TODO - need to unregister vbi? */
+			printk(KERN_INFO "%s: can't register radio device\n",
+				dev->name);
+			goto fail4;
+		}
+		printk(KERN_INFO "%s: registered device radio%d\n",
+		       dev->name, dev->radio_dev->num);
+	}
+
+	/* everything worked */
+	tw68_devcount++;
+
+	if (tw68_dmasound_init && !dev->dmasound.priv_data)
+		tw68_dmasound_init(dev);
+
+	return 0;
+
+ fail4:
+	tw68_unregister_video(dev);
+#ifdef TW68_TESTING
+	tw68_i2c_unregister(dev);
+#endif
+	free_irq(pci_dev->irq, dev);
+ fail3:
+	tw68_hwfini(dev);
+	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);
+ fail0:
+	kfree(dev);
+	return err;
+}
+
+static void __devexit 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);
+	struct tw68_mpeg_ops *mops;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	/* Release DMA sound modules if present */
+	if (tw68_dmasound_exit && dev->dmasound.priv_data)
+		tw68_dmasound_exit(dev);
+
+	/* shutdown subsystems */
+	tw68_hwfini(dev);
+	tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+	tw_writel(TW68_INTMASK, 0);
+
+	/* unregister */
+	mutex_lock(&tw68_devlist_lock);
+	list_del(&dev->devlist);
+	list_for_each_entry(mops, &mops_list, next)
+		mpeg_ops_detach(mops, dev);
+	mutex_unlock(&tw68_devlist_lock);
+	tw68_devcount--;
+
+#ifdef TW68_TESTING
+	tw68_i2c_unregister(dev);
+#endif
+	tw68_unregister_video(dev);
+
+
+	/* the DMA sound modules should be unloaded before reaching
+	   this, but just in case they are still present... */
+	if (dev->dmasound.priv_data != NULL) {
+		free_irq(pci_dev->irq, &dev->dmasound);
+		dev->dmasound.priv_data = NULL;
+	}
+
+
+	/* release resources */
+	free_irq(pci_dev->irq, dev);
+	iounmap(dev->lmmio);
+	release_mem_region(pci_resource_start(pci_dev, 0),
+			   pci_resource_len(pci_dev, 0));
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	/* free memory */
+	kfree(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);
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+	dev->pci_irqmask &= ~TW68_VID_INTS;
+	tw_writel(TW68_INTMASK, 0);
+
+	dev->insuspend = 1;
+	synchronize_irq(pci_dev->irq);
+
+	/* Disable timeout timers - if we have active buffers, we will
+	   fill them on resume*/
+
+	del_timer(&dev->video_q.timeout);
+	del_timer(&dev->vbi_q.timeout);
+	del_timer(&dev->ts_q.timeout);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
+   if (dev->remote)
+		tw68_ir_stop(dev);
+#endif
+
+	pci_save_state(pci_dev);
+	pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state));
+
+	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);
+	unsigned long flags;
+
+	dprintk(DBG_FLOW, "%s: called\n", __func__);
+	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.*/
+
+	tw68_board_init1(dev);
+
+	/* tw68_hw_init1 */
+	if (tw68_boards[dev->board].video_out)
+		tw68_videoport_init(dev);
+	if (card_has_mpeg(dev))
+		tw68_ts_init_hw(dev);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
+	if (dev->remote)
+		tw68_ir_start(dev, dev->remote);
+#endif
+	tw68_hw_enable1(dev);
+
+	msleep(100);
+
+	tw68_board_init2(dev);
+
+	/*tw68_hw_init2*/
+	tw68_set_tvnorm_hw(dev);
+	tw68_tvaudio_setmute(dev);
+/*	tw68_tvaudio_setvolume(dev, dev->ctl_volume); */
+	tw68_tvaudio_init(dev);
+	tw68_irq_video_signalchange(dev);
+
+	/*resume unfinished buffer(s)*/
+	spin_lock_irqsave(&dev->slock, flags);
+	tw68_buffer_requeue(dev, &dev->video_q);
+	tw68_buffer_requeue(dev, &dev->vbi_q);
+	tw68_buffer_requeue(dev, &dev->ts_q);
+
+	/* FIXME: Disable DMA audio sound - temporary till proper support
+		  is implemented*/
+
+	dev->dmasound.dma_running = 0;
+
+	/* start DMA now*/
+	dev->insuspend = 0;
+	smp_wmb();
+	tw68_set_dmabits(dev);
+	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	  = __devexit_p(tw68_finidev),
+#ifdef CONFIG_PM
+	.suspend  = tw68_suspend,
+	.resume   = tw68_resume
+#endif
+};
+
+static int tw68_init(void)
+{
+	if (core_debug & DBG_FLOW)
+		printk(KERN_DEBUG "%s: called\n", __func__);
+	INIT_LIST_HEAD(&tw68_devlist);
+	printk(KERN_INFO "tw68: v4l2 driver version %d.%d.%d loaded\n",
+		(TW68_VERSION_CODE >> 16) & 0xff,
+		(TW68_VERSION_CODE >> 8) & 0xff,
+		TW68_VERSION_CODE & 0xff);
+#if 0
+	printk(KERN_INFO "tw68: snapshot date %04d-%02d-%02d\n",
+		SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100);
+#endif
+	return pci_register_driver(&tw68_pci_driver);
+}
+
+static void module_cleanup(void)
+{
+	if (core_debug & DBG_FLOW)
+		printk(KERN_DEBUG "%s: called\n", __func__);
+	pci_unregister_driver(&tw68_pci_driver);
+}
+
+module_init(tw68_init);
+module_exit(module_cleanup);

+ 245 - 0
drivers/media/pci/tw68/tw68-i2c.c

@@ -0,0 +1,245 @@
+/*
+ *  tw68 code to handle the i2c interface.
+ *
+ *  Much of this code is derived from the bt87x driver.  The original
+ *  work was by Gerd Knorr; more recently the code was enhanced by Mauro
+ *  Carvalho Chehab.  Their work is gratefully acknowledged.  Full credit
+ *  goes to them - any problems within this code are mine.
+ *
+ *  Copyright (C) 2009  William M. Brack <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include "tw68.h"
+#include <media/v4l2-common.h>
+#include <linux/i2c-algo-bit.h>
+
+/*----------------------------------------------------------------*/
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#if 0
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+#endif
+
+#define d1printk if (1 == i2c_debug) printk
+
+#define	I2C_CLOCK	0xa6	/* 99.4 kHz */
+
+/*----------------------------------------------------------------------*/
+/* Although the TW68xx i2c controller has a "hardware" mode, where all of
+ * the low-level i2c/smbbus is handled by the chip, it appears that mode
+ * is not suitable for linux i2c handling routines because extended "bursts"
+ * of data (sequences of bytes without intervening START/STOP bits) are
+ * not possible.  Instead, we put the chip into "software" mode, and handle
+ * the i2c bus at a low level.  To accomplish this, we use the routines
+ * from the i2c modules.
+ *
+ * Because the particular boards which I had for testing did not have any
+ * devices attached to the i2c bus, I have been unable to test these
+ * routines.
+ */
+
+/*----------------------------------------------------------------------*/
+/* I2C functions - "bit-banging" adapter (software i2c) 		*/
+
+/* tw68_bit_setcl
+ * Handles "toggling" the i2c clock bit
+ */
+static void tw68_bit_setscl(void *data, int state)
+{
+	struct tw68_dev *dev = data;
+
+	tw_andorb(TW68_SBUSC, (state ? 1 : 0) << TW68_SSCLK, TW68_SSCLK_B);
+}
+
+/* tw68_bit_setsda
+ * Handles "toggling" the i2c data bit
+ */
+static void tw68_bit_setsda(void *data, int state)
+{
+	struct tw68_dev *dev = data;
+
+	tw_andorb(TW68_SBUSC, (state ? 1 : 0) << TW68_SSDAT, TW68_SSDAT_B);
+}
+
+/* tw68_bit_getscl
+ *
+ * Returns the current state of the clock bit
+ */
+static int tw68_bit_getscl(void *data)
+{
+	struct tw68_dev *dev = data;
+
+	return (tw_readb(TW68_SBUSC) & TW68_SSCLK_B) ? 1 : 0;
+}
+
+/* tw68_bit_getsda
+ *
+ * Returns the current state of the data bit
+ */
+static int tw68_bit_getsda(void *data)
+{
+	struct tw68_dev *dev = data;
+
+	return (tw_readb(TW68_SBUSC) & TW68_SSDAT_B) ? 1 : 0;
+}
+
+static struct i2c_algo_bit_data __devinitdata tw68_i2c_algo_bit_template = {
+	.setsda	 = tw68_bit_setsda,
+	.setscl	 = tw68_bit_setscl,
+	.getsda	 = tw68_bit_getsda,
+	.getscl	 = tw68_bit_getscl,
+	.udelay	 = 16,
+	.timeout = 200,
+};
+
+static struct i2c_client tw68_client_template = {
+	.name		= "tw68 internal",
+};
+
+/*----------------------------------------------------------------*/
+
+static int attach_inform(struct i2c_client *client)
+{
+/*	struct tw68_dev *dev = client->adapter->algo_data; */
+
+	d1printk("%s i2c attach [addr=0x%x,client=%s]\n",
+		client->driver->driver.name, client->addr, client->name);
+
+	switch (client->addr) {
+		/* No info yet on what addresses to expect */
+	}
+
+	return 0;
+}
+
+static struct i2c_adapter tw68_adap_sw_template = {
+	.owner		= THIS_MODULE,
+	.name		= "tw68_sw",
+	.client_register = attach_inform,
+};
+
+static int tw68_i2c_eeprom(struct tw68_dev *dev, unsigned char *eedata,
+			   int len)
+{
+	unsigned char buf;
+	int i, err;
+
+	dev->i2c_client.addr = 0xa0 >> 1;
+	buf = 256 - len;
+
+	err = i2c_master_send(&dev->i2c_client, &buf, 1);
+	if (1 != err) {
+		printk(KERN_INFO "%s: Huh, no eeprom present (err = %d)?\n",
+			dev->name, err);
+		return -1;
+	}
+	err = i2c_master_recv(&dev->i2c_client, eedata, len);
+	if (len != err) {
+		printk(KERN_WARNING "%s: i2c eeprom read error (err=%d)\n",
+			dev->name, err);
+		return -1;
+	}
+
+	for (i = 0; i < len; i++) {
+		if (0 == (i % 16))
+			printk(KERN_INFO "%s: i2c eeprom %02x:",
+				dev->name, i);
+		printk(KERN_INFO " %02x", eedata[i]);
+		if (15 == (i % 16))
+			printk("\n");
+	}
+	return 0;
+}
+
+#if 0
+static char *i2c_devs[128] = {
+	[0xa0 >> 1] = "eeprom",
+};
+
+static void do_i2c_scan(char *name, struct i2c_client *c)
+{
+	unsigned char buf;
+	int i, rc;
+
+	for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) {
+		c->addr = i;
+		rc = i2c_master_recv(c, &buf, 1);
+		if (rc < 0)
+			continue;
+		printk(KERN_INFO "%s: i2c scan: found device "
+				 "@ 0x%x [%s]\n", name, i << 1,
+				 i2c_devs[i] ? i2c_devs[i] : "???");
+	}
+}
+#endif
+
+int __devinit tw68_i2c_register(struct tw68_dev *dev)
+{
+	int rc;
+
+printk(KERN_DEBUG "%s: Registering i2c module\n", __func__);
+	tw_writeb(TW68_I2C_RST, 1);	/* reset the i2c module */
+
+	memcpy(&dev->i2c_client, &tw68_client_template,
+		sizeof(tw68_client_template));
+
+	memcpy(&dev->i2c_adap, &tw68_adap_sw_template,
+		sizeof(tw68_adap_sw_template));
+	dev->i2c_adap.algo_data = &dev->i2c_algo;
+	dev->i2c_adap.dev.parent = &dev->pci->dev;
+
+	memcpy(&dev->i2c_algo, &tw68_i2c_algo_bit_template,
+		sizeof(tw68_i2c_algo_bit_template));
+	dev->i2c_algo.data = dev;
+	/* TODO - may want to set better name (see bttv code) */
+
+	i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev);
+	dev->i2c_client.adapter = &dev->i2c_adap;
+
+	/* Assure chip is in "software" mode */
+	tw_writel(TW68_SBUSC, TW68_SSDAT | TW68_SSCLK);
+	tw68_bit_setscl(dev, 1);
+	tw68_bit_setsda(dev, 1);
+
+	rc = i2c_bit_add_bus(&dev->i2c_adap);
+
+	tw68_i2c_eeprom(dev, dev->eedata, sizeof(dev->eedata));
+#if 0
+	if (i2c_scan)
+		do_i2c_scan(dev->name, &dev->i2c_client);
+#endif
+
+	return rc;
+}
+
+int tw68_i2c_unregister(struct tw68_dev *dev)
+{
+	i2c_del_adapter(&dev->i2c_adap);
+	return 0;
+}

+ 195 - 0
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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#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

+ 268 - 0
drivers/media/pci/tw68/tw68-risc.c

@@ -0,0 +1,268 @@
+/*
+ *  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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tw68.h"
+
+#define NO_SYNC_LINE (-1U)
+
+/**
+ *  @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
+ *  @lpi	lines per IRQ, or 0 to not generate irqs
+ *		Note: IRQ to be generated _after_ lpi lines are transferred
+ */
+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, unsigned int lpi)
+{
+	struct scatterlist *sg;
+	unsigned int line, todo, done;
+
+	/* sync instruction */
+	if (sync_line != NO_SYNC_LINE) {
+		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++;
+		}
+		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++;
+			/* 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++;
+				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;
+		/* If this line needs an interrupt, put it in */
+		if (lpi && line > 0 && !(line % lpi))
+			*(rp-2) |= RISC_INT_BIT;
+	}
+
+	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 btcx_riscmem *risc,
+			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;
+	int rc;
+
+	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 + jump (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) + 2;
+	rc = btcx_riscmem_alloc(pci, risc, instructions * 8);
+	if (rc < 0)
+		return rc;
+
+	/* write risc instructions */
+	rp = risc->cpu;
+	if (UNSET != top_offset)	/* generates SYNCO */
+		rp = tw68_risc_field(rp, sglist, top_offset, 1,
+				     bpl, padding, lines, 0);
+	if (UNSET != bottom_offset)	/* generates SYNCE */
+		rp = tw68_risc_field(rp, sglist, bottom_offset, 2,
+				     bpl, padding, lines, 0);
+
+	/* save pointer to jmp instruction address */
+	risc->jmp = rp;
+	/* assure risc buffer hasn't overflowed */
+	BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->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) {
+		printk(KERN_DEBUG "0x%08x [ INVALID ]\n", risc);
+		return;
+	}
+	printk(KERN_DEBUG "0x%08x %-9s IRQ=%d",
+		risc, instr[p].name, (risc >> 27) & 1);
+	if (instr[p].has_data_type)
+		printk(KERN_DEBUG " Type=%d", (risc >> 24) & 7);
+	if (instr[p].has_byte_info)
+		printk(KERN_DEBUG " Start=0x%03x Count=%03u",
+			(risc >> 12) & 0xfff, risc & 0xfff);
+	if (instr[p].has_addr)
+		printk(KERN_DEBUG " StartAddr=0x%08x", addr);
+	printk(KERN_DEBUG "\n");
+}
+
+void tw68_risc_program_dump(struct tw68_core *core,
+			    struct btcx_riscmem *risc)
+{
+	__le32 *addr;
+
+	printk(KERN_DEBUG "%s: risc_program_dump: risc=%p, "
+			  "risc->cpu=0x%p, risc->jmp=0x%p\n",
+			  core->name, risc, risc->cpu, risc->jmp);
+	for (addr = risc->cpu; addr <= risc->jmp; addr += 2)
+		tw68_risc_decode(*addr, *(addr+1));
+}
+EXPORT_SYMBOL_GPL(tw68_risc_program_dump);
+#endif
+
+/*
+ * tw68_risc_stopper
+ * 	Normally, the risc code generated for a buffer ends with a
+ * 	JUMP instruction to direct the DMAP processor to the code for
+ * 	the next buffer.  However, when there is no additional buffer
+ * 	currently available, the code instead jumps to this routine.
+ *
+ * 	My first try for a "stopper" program was just a simple
+ * 	"jump to self" instruction.  Unfortunately, this caused the
+ * 	video FIFO to overflow.  My next attempt was to just disable
+ * 	the DMAP processor.  Unfortunately, this caused the video
+ * 	decoder to lose its synchronization.  The solution to this was to
+ * 	add a "Sync-Odd" instruction, which "eats" all the video data
+ * 	until the start of the next odd field.
+ */
+int tw68_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc)
+{
+	__le32 *rp;
+	int rc;
+
+	rc = btcx_riscmem_alloc(pci, risc, 8*4);
+	if (rc < 0)
+		return rc;
+
+	/* write risc inststructions */
+	rp = risc->cpu;
+	*(rp++) = cpu_to_le32(RISC_SYNCO);
+	*(rp++) = 0;
+	*(rp++) = cpu_to_le32(RISC_JUMP);
+	*(rp++) = cpu_to_le32(risc->dma);
+	risc->jmp = risc->cpu;
+	return 0;
+}

+ 66 - 0
drivers/media/pci/tw68/tw68-ts.c

@@ -0,0 +1,66 @@
+/*
+ *  tw68_ts.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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tw68.h"
+
+int tw68_ts_init1(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+int tw68_ts_ini(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+int tw68_ts_fini(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+void tw68_irq_ts_done(struct tw68_dev *dev, unsigned long status)
+{
+	return;
+}
+
+int tw68_ts_register(struct tw68_mpeg_ops *ops)
+{
+	return 0;
+}
+
+void tw68_ts_unregister(struct tw68_mpeg_ops *ops)
+{
+	return;
+}
+
+int tw68_ts_init_hw(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+

+ 80 - 0
drivers/media/pci/tw68/tw68-tvaudio.c

@@ -0,0 +1,80 @@
+/*
+ *  tw68_controls.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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tw68.h"
+
+int tw68_tvaudio_rx2mode(u32 rx)
+{
+	return 0;
+}
+
+void tw68_tvaudio_setmute(struct tw68_dev *dev)
+{
+	return;
+}
+
+void tw68_tvaudio_setinput(struct tw68_dev *dev, struct tw68_input *in)
+{
+	return;
+}
+
+void tw68_tvaudio_setvolume(struct tw68_dev *dev, int level)
+{
+	return;
+}
+
+int tw68_tvaudio_getstereo(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+void tw68_tvaudio_init(struct tw68_dev *dev)
+{
+	return;
+}
+
+int tw68_tvaudio_init2(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+int tw68_tvaudio_fini(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+int tw68_tvaudio_do_scan(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+void tw68_enable_i2s(struct tw68_dev *dev)
+{
+	return;
+}
+

+ 76 - 0
drivers/media/pci/tw68/tw68-vbi.c

@@ -0,0 +1,76 @@
+/*
+ *  tw68_controls.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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tw68.h"
+
+static int buffer_setup(struct videobuf_queue *q, unsigned int *count,
+			unsigned int *size) {
+	printk(KERN_INFO "%s: shouldn't be here!\n", __func__);
+	return 0;
+}
+static int buffer_prepare(struct videobuf_queue *q,
+			  struct videobuf_buffer *vb,
+			  enum v4l2_field field)
+{
+	printk(KERN_INFO "%s: shouldn't be here!\n", __func__);
+	return 0;
+}
+static void buffer_queue(struct videobuf_queue *q,
+			 struct videobuf_buffer *vb)
+{
+	printk(KERN_INFO "%s: shouldn't be here!\n", __func__);
+}
+static void buffer_release(struct videobuf_queue *q,
+			   struct videobuf_buffer *vb)
+{
+	printk(KERN_INFO "%s: shouldn't be here!\n", __func__);
+}
+struct videobuf_queue_ops tw68_vbi_qops = {
+	.buf_setup    = buffer_setup,
+	.buf_prepare  = buffer_prepare,
+	.buf_queue    = buffer_queue,
+	.buf_release  = buffer_release,
+};
+
+/* ------------------------------------------------------------------ */
+
+int tw68_vbi_init1(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+int tw68_vbi_fini(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+void tw68_irq_vbi_done(struct tw68_dev *dev, unsigned long status)
+{
+	return;
+}
+

+ 2230 - 0
drivers/media/pci/tw68/tw68-video.c

@@ -0,0 +1,2230 @@
+/*
+ *  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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <media/v4l2-common.h>
+#include <linux/sort.h>
+
+#include "tw68.h"
+#include "tw68-reg.h"
+
+unsigned int video_debug;
+
+static unsigned int gbuffers	= 8;
+static unsigned int noninterlaced; /* 0 */
+static unsigned int gbufsz	= 768*576*4;
+static unsigned int gbufsz_max	= 768*576*4;
+static char secam[]		= "--";
+
+module_param(video_debug, int, 0644);
+MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
+module_param(gbuffers, int, 0444);
+MODULE_PARM_DESC(gbuffers, "number of capture buffers, range 2-32");
+module_param(noninterlaced, int, 0644);
+MODULE_PARM_DESC(noninterlaced, "capture non interlaced video");
+module_param_string(secam, secam, sizeof(secam), 0644);
+MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc");
+
+#define dprintk(level, fmt, arg...)     if (video_debug & (level)) \
+	printk(KERN_DEBUG "%s/0: " fmt, dev->name , ## arg)
+
+/* ------------------------------------------------------------------ */
+/* 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 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 struct tw68_tvnorm tvnorms[]		= {
+	{
+		.name		= "PAL-BG",
+		.id		= V4L2_STD_PAL_BG,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x40,
+		.chroma_ctrl1	= 0x81,
+		.chroma_gain	= 0x2a,
+		.chroma_ctrl2	= 0x06,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatPALBDGHI,
+
+	}, {
+		.name		= "PAL-I",
+		.id		= V4L2_STD_PAL_I,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x40,
+		.chroma_ctrl1	= 0x81,
+		.chroma_gain	= 0x2a,
+		.chroma_ctrl2	= 0x06,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatPALBDGHI,
+
+	}, {
+		.name		= "PAL-DK",
+		.id		= V4L2_STD_PAL_DK,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x40,
+		.chroma_ctrl1	= 0x81,
+		.chroma_gain	= 0x2a,
+		.chroma_ctrl2	= 0x06,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatPALBDGHI,
+
+	}, {
+		.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-DK",
+		.id		= V4L2_STD_SECAM_DK,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x1b,
+		.chroma_ctrl1	= 0xd1,
+		.chroma_gain	= 0x80,
+		.chroma_ctrl2	= 0x00,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatSECAM,
+
+	}, {
+		.name		= "SECAM-L",
+		.id		= V4L2_STD_SECAM_L,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x1b,
+		.chroma_ctrl1	= 0xd1,
+		.chroma_gain	= 0x80,
+		.chroma_ctrl2	= 0x00,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatSECAM,
+
+	}, {
+		.name		= "SECAM-LC",
+		.id		= V4L2_STD_SECAM_LC,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x1b,
+		.chroma_ctrl1	= 0xd1,
+		.chroma_gain	= 0x80,
+		.chroma_ctrl2	= 0x00,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatSECAM,
+
+	}, {
+		.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,
+
+	}, {
+/*
+ * 	FIXME:  The following are meant to be "catch-all", and need
+ *		to be further thought out!
+ */
+		.name		= "STD-525-60",
+		.id		= V4L2_STD_525_60,
+		NORM_525_60,
+
+		.sync_control	= 0x59,
+		.luma_control	= 0x40,
+		.chroma_ctrl1	= 0x89,
+		.chroma_gain	= 0x2a,
+		.chroma_ctrl2	= 0x0e,
+		.vgate_misc	= 0x18,
+		.format		= VideoFormatNTSC,
+
+	}, {
+		.name		= "STD-625-50",
+		.id		= V4L2_STD_625_50,
+		NORM_625_50,
+
+		.sync_control	= 0x18,
+		.luma_control	= 0x40,
+		.chroma_ctrl1	= 0x81,
+		.chroma_gain	= 0x2a,
+		.chroma_ctrl2	= 0x06,
+		.vgate_misc	= 0x1c,
+		.format		= VideoFormatPALBDGHI,
+	}
+};
+#define TVNORMS ARRAY_SIZE(tvnorms)
+
+static const struct v4l2_queryctrl no_ctrl		= {
+	.name		= "42",
+	.flags		= V4L2_CTRL_FLAG_DISABLED,
+};
+static const struct v4l2_queryctrl video_ctrls[]		= {
+	/* --- video --- */
+	{
+		.id		= V4L2_CID_BRIGHTNESS,
+		.name		= "Brightness",
+		.minimum	= -128,
+		.maximum	= 127,
+		.step		= 1,
+		.default_value	= 20,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+	}, {
+		.id		= V4L2_CID_CONTRAST,
+		.name		= "Contrast",
+		.minimum	= 0,
+		.maximum	= 255,
+		.step		= 1,
+		.default_value	= 100,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+	}, {
+		.id		= V4L2_CID_SATURATION,
+		.name		= "Saturation",
+		.minimum	= 0,
+		.maximum	= 255,
+		.step		= 1,
+		.default_value	= 128,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+	}, {
+		.id		= V4L2_CID_HUE,
+		.name		= "Hue",
+		.minimum	= -128,
+		.maximum	= 127,
+		.step		= 1,
+		.default_value	= 0,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+	}, {
+		.id		= V4L2_CID_COLOR_KILLER,
+		.name		= "Color Killer",
+		.minimum	= 0,
+		.maximum	= 1,
+		.default_value	= 1,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+	}, {
+		.id		= V4L2_CID_CHROMA_AGC,
+		.name		= "Chroma AGC",
+		.minimum	= 0,
+		.maximum	= 1,
+		.default_value	= 1,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+	},
+	/* --- audio --- */
+	{
+		.id		= V4L2_CID_AUDIO_MUTE,
+		.name		= "Mute",
+		.minimum	= 0,
+		.maximum	= 1,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+	}, {
+		.id		= V4L2_CID_AUDIO_VOLUME,
+		.name		= "Volume",
+		.minimum	= -15,
+		.maximum	= 15,
+		.step		= 1,
+		.default_value	= 0,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+	}
+};
+static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls);
+
+/*
+ * Routine to lookup a control by its ID, and return a pointer
+ * to the entry in the video_ctrls array for that control.
+ */
+static const struct v4l2_queryctrl *ctrl_by_id(unsigned int id)
+{
+	unsigned int i;
+
+	for (i = 0; i < CTRLS; i++)
+		if (video_ctrls[i].id == id)
+			return video_ctrls+i;
+	return NULL;
+}
+
+static 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;
+}
+
+/* ----------------------------------------------------------------------- */
+/* resource management                                                     */
+
+static int res_get(struct tw68_fh *fh, unsigned int bit)
+{
+	struct tw68_dev *dev = fh->dev;
+
+	if (fh->resources & bit)
+		/* have it already allocated */
+		return 1;
+
+	/* is it free? */
+	mutex_lock(&dev->lock);
+	if (dev->resources & bit) {
+		/* no, someone else uses it */
+		mutex_unlock(&fh->dev->lock);
+		return 0;
+	}
+	/* it's free, grab it */
+	fh->resources  |= bit;
+	dev->resources |= bit;
+	dprintk(DBG_FLOW, "%s: %d\n", __func__, bit);
+	mutex_unlock(&dev->lock);
+	return 1;
+}
+
+static int res_check(struct tw68_fh *fh, unsigned int bit)
+{
+	return fh->resources & bit;
+}
+
+static int res_locked(struct tw68_dev *dev, unsigned int bit)
+{
+	return dev->resources & bit;
+}
+
+static void res_free(struct tw68_fh *fh,
+		     unsigned int bits)
+{
+	struct tw68_dev *dev = fh->dev;
+
+	BUG_ON((fh->resources & bits) != bits);
+
+	mutex_lock(&fh->dev->lock);
+	fh->resources  &= ~bits;
+	fh->dev->resources &= ~bits;
+	dprintk(DBG_FLOW, "%s: %d\n", __func__, bits);
+	mutex_unlock(&fh->dev->lock);
+}
+
+/* ------------------------------------------------------------------ */
+/*
+ * 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, struct tw68_tvnorm *norm)
+{
+	dprintk(DBG_FLOW, "%s: %s\n", __func__, norm->name);
+	dev->tvnorm = norm;
+
+	/* setup cropping */
+	dev->crop_bounds.left    = norm->h_start;
+	dev->crop_defrect.left   = norm->h_start;
+	dev->crop_bounds.width   = norm->h_stop - norm->h_start + 1;
+	dev->crop_defrect.width  = norm->h_stop - norm->h_start + 1;
+
+	dev->crop_bounds.top     = norm->video_v_start;
+	dev->crop_defrect.top    = norm->video_v_start;
+	dev->crop_bounds.height  = (((norm->id & V4L2_STD_525_60) ?
+				    524 : 624)) / 2 - dev->crop_bounds.top;
+	dev->crop_defrect.height = (norm->video_v_stop -
+				    norm->video_v_start + 1);
+
+	dev->crop_current = dev->crop_defrect;
+
+	if (norm != dev->tvnorm) {
+		dev->tvnorm = norm;
+		tw68_set_tvnorm_hw(dev);
+	}
+}
+
+static void video_mux(struct tw68_dev *dev, int input)
+{
+	dprintk(DBG_FLOW, "%s: input = %d [%s]\n", __func__, input,
+		card_in(dev, input).name);
+	/*
+	 * dev->input shows current application request,
+	 * dev->hw_input shows current hardware setting
+	 */
+	dev->input = &card_in(dev, input);
+	tw68_tvaudio_setinput(dev, &card_in(dev, input));
+}
+
+/*
+ * 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)
+{
+
+	/* 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 */
+
+	dprintk(DBG_FLOW, "%s: width=%d, height=%d, both=%d\n  Crop rect: "
+		    "top=%d, left=%d, width=%d height=%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),
+		dev->crop_bounds.top, dev->crop_bounds.left,
+		dev->crop_bounds.width, dev->crop_bounds.height,
+		dev->tvnorm->h_delay, dev->tvnorm->h_start, dev->tvnorm->h_stop,
+		dev->tvnorm->v_delay, dev->tvnorm->video_v_start,
+		dev->tvnorm->video_v_stop);
+
+	switch (dev->vdecoder) {
+	case TW6800:
+		hdelay = dev->tvnorm->h_delay0;
+		break;
+	default:
+		hdelay = dev->tvnorm->h_delay;
+		break;
+	}
+	hdelay += dev->crop_bounds.left;
+	hactive = dev->crop_bounds.width;
+
+	hscale = (hactive * 256) / (width);
+
+	vdelay = dev->tvnorm->v_delay + dev->crop_bounds.top -
+		 dev->crop_defrect.top;
+	vactive = dev->crop_bounds.height;
+	vscale = (vactive * 256) / height;
+
+	dprintk(DBG_FLOW, "%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));
+	dprintk(DBG_FLOW, "%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);
+	dprintk(DBG_FLOW, "%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);
+	dprintk(DBG_FLOW, "%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;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_dmaqueue *q,
+				struct tw68_buf *buf) {
+
+	dprintk(DBG_FLOW, "%s: Starting risc program\n", __func__);
+	/* Assure correct input */
+	if (dev->hw_input != dev->input) {
+		dev->hw_input = dev->input;
+		tw_andorb(TW68_INFORM, 0x03 << 2, dev->input->vmux << 2);
+	}
+	/* Set cropping and scaling */
+	tw68_set_scale(dev, buf->vb.width, buf->vb.height, buf->vb.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->risc.dma));
+	/* Clear any pending interrupts */
+	tw_writel(TW68_INTSTAT, dev->board_virqmask);
+	/* Enable the risc engine and the fifo */
+	tw_andorl(TW68_DMAC, 0xff, buf->fmt->twformat |
+		ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
+	dev->pci_irqmask |= dev->board_virqmask;
+	tw_setl(TW68_INTMASK, dev->pci_irqmask);
+	return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* videobuf queue operations                                          */
+
+/*
+ * check_buf_fmt
+ *
+ * callback from tw68-core buffer_queue to determine whether the
+ * current buffer and the previous one are "compatible" (i.e. the
+ * risc programs can be chained without requiring a format change)
+ */
+static int tw68_check_video_fmt(struct tw68_buf *prev, struct tw68_buf *buf)
+{
+	return (prev->vb.width  == buf->vb.width  &&
+		prev->vb.height == buf->vb.height &&
+		prev->fmt       == buf->fmt);
+}
+
+/*
+ * buffer_setup
+ *
+ * Calculate required size of buffer and maximum number allowed
+ */
+static int
+buffer_setup(struct videobuf_queue *q, unsigned int *count,
+	     unsigned int *size)
+{
+	struct tw68_fh *fh = q->priv_data;
+
+	*size = fh->fmt->depth * fh->width * fh->height >> 3;
+	if (0 == *count)
+		*count = gbuffers;
+	*count = tw68_buffer_count(*size, *count);
+	return 0;
+}
+
+static int buffer_activate(struct tw68_dev *dev, struct tw68_buf *buf,
+			   struct tw68_buf *next)
+{
+	dprintk(DBG_BUFF, "%s: dev=%p, buf=%p, next=%p\n",
+		__func__, dev, buf, next);
+	if (dev->hw_input != dev->input) {
+		dev->hw_input = dev->input;
+		tw_andorb(TW68_INFORM, 0x03 << 2,
+			  dev->hw_input->vmux << 2);
+	}
+	buf->vb.state = VIDEOBUF_ACTIVE;
+	/* TODO - need to assure scaling/cropping are set correctly */
+	mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT);
+	return 0;
+}
+
+/*
+* 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
+buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb,
+	       enum v4l2_field field)
+{
+	struct tw68_fh   *fh  = q->priv_data;
+	struct tw68_dev  *dev = fh->dev;
+	struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+	struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb);
+	int rc, init_buffer = 0;
+	unsigned int maxw, maxh;
+
+	BUG_ON(NULL == fh->fmt);
+	maxw = dev->tvnorm->h_stop - dev->tvnorm->h_start + 1;
+	maxh = 2*(dev->tvnorm->video_v_stop - dev->tvnorm->video_v_start + 1);
+	if (fh->width  < 48 || fh->width  > maxw || fh->height > maxh
+		|| fh->height < 16) {
+		dprintk(DBG_UNEXPECTED, "%s: invalid dimensions - "
+			"fh->width=%d, fh->height=%d, maxw=%d, maxh=%d\n",
+			__func__, fh->width, fh->height, maxw, maxh);
+		return -EINVAL;
+	}
+	buf->vb.size = (fh->width * fh->height * (fh->fmt->depth)) >> 3;
+	if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size)
+		return -EINVAL;
+
+	if (buf->fmt       != fh->fmt    ||
+	    buf->vb.width  != fh->width  ||
+	    buf->vb.height != fh->height ||
+	    buf->vb.field  != field) {
+		dprintk(DBG_BUFF, "%s: buf - fmt=%p, width=%3d, height=%3d, "
+			"field=%d\n%s: fh  - fmt=%p, width=%3d, height=%3d, "
+			"field=%d\n", __func__, buf->fmt, buf->vb.width,
+			buf->vb.height, buf->vb.field, __func__, fh->fmt,
+			fh->width, fh->height, field);
+		buf->fmt       = fh->fmt;
+		buf->vb.width  = fh->width;
+		buf->vb.height = fh->height;
+		buf->vb.field  = field;
+		init_buffer = 1;	/* force risc code re-generation */
+	}
+	buf->input = dev->input;
+
+	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+		rc = videobuf_iolock(q, &buf->vb, NULL);
+		if (0 != rc)
+			goto fail;
+		init_buffer = 1;	/* force risc code re-generation */
+	}
+	dprintk(DBG_BUFF, "%s: q=%p, vb=%p, init_buffer=%d\n",
+		__func__, q, vb, init_buffer);
+
+	if (init_buffer) {
+		buf->bpl = buf->vb.width * (buf->fmt->depth) >> 3;
+		dprintk(DBG_TESTING, "%s: Generating new risc code "
+			"[%dx%dx%d](%d)\n", __func__, buf->vb.width,
+			buf->vb.height, buf->fmt->depth, buf->bpl);
+		switch (buf->vb.field) {
+		case V4L2_FIELD_TOP:
+			tw68_risc_buffer(dev->pci, &buf->risc,
+					 dma->sglist,
+					 0, UNSET,
+					 buf->bpl, 0,
+					 buf->vb.height);
+			break;
+		case V4L2_FIELD_BOTTOM:
+			tw68_risc_buffer(dev->pci, &buf->risc,
+					 dma->sglist,
+					 UNSET, 0,
+					 buf->bpl, 0,
+					 buf->vb.height);
+			break;
+		case V4L2_FIELD_INTERLACED:
+			tw68_risc_buffer(dev->pci, &buf->risc,
+					 dma->sglist,
+					 0, buf->bpl,
+					 buf->bpl, buf->bpl,
+					 buf->vb.height >> 1);
+			break;
+		case V4L2_FIELD_SEQ_TB:
+			tw68_risc_buffer(dev->pci, &buf->risc,
+					 dma->sglist,
+					 0, buf->bpl * (buf->vb.height >> 1),
+					 buf->bpl, 0,
+					 buf->vb.height >> 1);
+			break;
+		case V4L2_FIELD_SEQ_BT:
+			tw68_risc_buffer(dev->pci, &buf->risc,
+					 dma->sglist,
+					 buf->bpl * (buf->vb.height >> 1), 0,
+					 buf->bpl, 0,
+					 buf->vb.height >> 1);
+			break;
+		default:
+			BUG();
+		}
+	}
+	dprintk(DBG_BUFF, "%s: [%p/%d] - %dx%d %dbpp \"%s\" - dma=0x%08lx\n",
+		__func__, buf, buf->vb.i, fh->width, fh->height,
+		fh->fmt->depth, fh->fmt->name, (unsigned long)buf->risc.dma);
+
+	buf->vb.state = VIDEOBUF_PREPARED;
+	buf->activate = buffer_activate;
+	return 0;
+
+ fail:
+	tw68_dma_free(q, buf);
+	return rc;
+}
+
+/*
+ * buffer_queue
+ *
+ * Callback whenever a buffer has been requested (by read() or QBUF)
+ */
+static void
+buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb)
+{
+	struct tw68_fh	*fh = q->priv_data;
+	struct tw68_buf	*buf = container_of(vb, struct tw68_buf, vb);
+
+	tw68_buffer_queue(fh->dev, &fh->dev->video_q, buf);
+}
+
+/*
+ * buffer_release
+ *
+ * Free a buffer previously allocated.
+ */
+static void buffer_release(struct videobuf_queue *q,
+			   struct videobuf_buffer *vb)
+{
+	struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
+
+	tw68_dma_free(q, buf);
+}
+
+static struct videobuf_queue_ops video_qops = {
+	.buf_setup    = buffer_setup,
+	.buf_prepare  = buffer_prepare,
+	.buf_queue    = buffer_queue,
+	.buf_release  = buffer_release,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int tw68_g_ctrl_internal(struct tw68_dev *dev, struct tw68_fh *fh,
+				struct v4l2_control *c)
+{
+	const struct v4l2_queryctrl *ctrl;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	ctrl = ctrl_by_id(c->id);
+	if (NULL == ctrl)
+		return -EINVAL;
+	switch (c->id) {
+	case V4L2_CID_BRIGHTNESS:
+		c->value = (char)tw_readb(TW68_BRIGHT);
+		break;
+	case V4L2_CID_HUE:
+		c->value = (char)tw_readb(TW68_HUE);
+		break;
+	case V4L2_CID_CONTRAST:
+		c->value = tw_readb(TW68_CONTRAST);
+		break;
+	case V4L2_CID_SATURATION:
+		c->value = tw_readb(TW68_SAT_U);
+		break;
+	case V4L2_CID_COLOR_KILLER:
+		c->value = 0 != (tw_readb(TW68_MISC2) & 0xe0);
+		break;
+	case V4L2_CID_CHROMA_AGC:
+		c->value = 0 != (tw_readb(TW68_LOOP) & 0x30);
+		break;
+	case V4L2_CID_AUDIO_MUTE:
+		/*hack to suppresss tvtime complaint */
+		c->value = 0;
+		break;
+#if 0
+	case V4L2_CID_AUDIO_VOLUME:
+		c->value = dev->ctl_volume;
+		break;
+#endif
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int tw68_g_ctrl(struct file *file, void *priv, struct v4l2_control *c)
+{
+	struct tw68_fh *fh = priv;
+
+	return tw68_g_ctrl_internal(fh->dev, fh, c);
+}
+
+static int tw68_s_ctrl_value(struct tw68_dev *dev, __u32 id, int val)
+{
+	int err = 0;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	switch (id) {
+	case V4L2_CID_BRIGHTNESS:
+		tw_writeb(TW68_BRIGHT, val);
+		break;
+	case V4L2_CID_HUE:
+		tw_writeb(TW68_HUE, val);
+		break;
+	case V4L2_CID_CONTRAST:
+		tw_writeb(TW68_CONTRAST, val);
+		break;
+	case V4L2_CID_SATURATION:
+		tw_writeb(TW68_SAT_U, val);
+		tw_writeb(TW68_SAT_V, val);
+		break;
+	case V4L2_CID_COLOR_KILLER:
+		if (val)
+			tw_andorb(TW68_MISC2, 0xe0, 0xe0);
+		else
+			tw_andorb(TW68_MISC2, 0xe0, 0x00);
+		break;
+	case V4L2_CID_CHROMA_AGC:
+		if (val)
+			tw_andorb(TW68_LOOP, 0x30, 0x20);
+		else
+			tw_andorb(TW68_LOOP, 0x30, 0x00);
+		break;
+	case V4L2_CID_AUDIO_MUTE:
+		/* hack to suppress tvtime complaint */
+		break;
+#if 0
+	case V4L2_CID_AUDIO_VOLUME:
+		dev->ctl_volume = val;
+		tw68_tvaudio_setvolume(dev, dev->ctl_volume);
+		break;
+	case V4L2_CID_HFLIP:
+		dev->ctl_mirror = val;
+		break;
+	case V4L2_CID_PRIVATE_AUTOMUTE:
+	{
+		struct v4l2_priv_tun_config tda9887_cfg;
+
+		tda9887_cfg.tuner = TUNER_TDA9887;
+		tda9887_cfg.priv = &dev->tda9887_conf;
+
+		dev->ctl_automute = val;
+		if (dev->tda9887_conf) {
+			if (dev->ctl_automute)
+				dev->tda9887_conf |= TDA9887_AUTOMUTE;
+			else
+				dev->tda9887_conf &= ~TDA9887_AUTOMUTE;
+
+			tw_call_all(dev, tuner, s_config, &tda9887_cfg);
+		}
+		break;
+	}
+#endif
+	default:
+		err = -EINVAL;
+	}
+	return err;
+}
+
+static int tw68_s_ctrl_internal(struct tw68_dev *dev,  struct tw68_fh *fh,
+			 struct v4l2_control *c)
+{
+	const struct v4l2_queryctrl *ctrl;
+	int err;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (fh) {
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34)
+		err = v4l2_prio_check(&dev->prio, &fh->prio);
+#else
+		err = v4l2_prio_check(&dev->prio, fh->prio);
+#endif
+		if (0 != err)
+			return err;
+	}
+
+	mutex_lock(&dev->lock);
+
+	ctrl = ctrl_by_id(c->id);
+	if (NULL == ctrl) {
+		err = -EINVAL;
+		goto error;
+	}
+
+	dprintk(DBG_BUFF, "%s: name=%s val=%d\n", __func__,
+		ctrl->name, c->value);
+	switch (ctrl->type) {
+	case V4L2_CTRL_TYPE_BOOLEAN:
+	case V4L2_CTRL_TYPE_MENU:
+	case V4L2_CTRL_TYPE_INTEGER:
+		if (c->value < ctrl->minimum)
+			c->value = ctrl->minimum;
+		if (c->value > ctrl->maximum)
+			c->value = ctrl->maximum;
+		break;
+	default:
+		/* nothing */;
+	};
+	err = tw68_s_ctrl_value(dev, c->id, c->value);
+
+error:
+	mutex_unlock(&dev->lock);
+	return err;
+}
+
+static int tw68_s_ctrl(struct file *file, void *f, struct v4l2_control *c)
+{
+	struct tw68_fh *fh = f;
+
+	return tw68_s_ctrl_internal(fh->dev, fh, c);
+}
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * Returns a pointer to the currently used queue (e.g. video, vbi, etc.)
+ */
+static struct videobuf_queue *tw68_queue(struct tw68_fh *fh)
+{
+	struct videobuf_queue *q = NULL;
+
+	switch (fh->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		q = &fh->cap;
+		break;
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+		q = &fh->vbi;
+		break;
+	default:
+		BUG();
+	}
+	return q;
+}
+
+static int tw68_resource(struct tw68_fh *fh)
+{
+	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return RESOURCE_VIDEO;
+
+	if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
+		return RESOURCE_VBI;
+
+	BUG();
+	return 0;
+}
+
+static int video_open(struct file *file)
+{
+	int minor = video_devdata(file)->minor;
+	struct tw68_dev *dev;
+	struct tw68_fh *fh;
+	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	int radio = 0;
+
+	mutex_lock(&tw68_devlist_lock);
+	list_for_each_entry(dev, &tw68_devlist, devlist) {
+		if (dev->video_dev && (dev->video_dev->minor == minor))
+			goto found;
+		if (dev->radio_dev && (dev->radio_dev->minor == minor)) {
+			radio = 1;
+			goto found;
+		}
+		if (dev->vbi_dev && (dev->vbi_dev->minor == minor)) {
+			type = V4L2_BUF_TYPE_VBI_CAPTURE;
+			goto found;
+		}
+	}
+	mutex_unlock(&tw68_devlist_lock);
+	return -ENODEV;
+
+found:
+	mutex_unlock(&tw68_devlist_lock);
+
+	dprintk(DBG_FLOW, "%s: minor=%d radio=%d type=%s\n", __func__, minor,
+		radio, v4l2_type_names[type]);
+
+	/* allocate + initialize per filehandle data */
+	fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+	if (NULL == fh)
+		return -ENOMEM;
+
+	file->private_data = fh;
+	fh->dev      = dev;
+	fh->radio    = radio;
+	fh->type     = type;
+	fh->fmt      = format_by_fourcc(V4L2_PIX_FMT_BGR24);
+	fh->width    = 720;
+	fh->height   = 576;
+	v4l2_prio_open(&dev->prio, &fh->prio);
+
+	videobuf_queue_sg_init(&fh->cap, &video_qops,
+			    &dev->pci->dev, &dev->slock,
+			    V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			    V4L2_FIELD_INTERLACED,
+			    sizeof(struct tw68_buf),
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37)
+			    fh
+#else
+			    fh, &dev->lock
+#endif
+             );
+	videobuf_queue_sg_init(&fh->vbi, &tw68_vbi_qops,
+			    &dev->pci->dev, &dev->slock,
+			    V4L2_BUF_TYPE_VBI_CAPTURE,
+			    V4L2_FIELD_SEQ_TB,
+			    sizeof(struct tw68_buf),
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37)
+			    fh
+#else
+			    fh, &dev->lock
+#endif
+             );
+	if (fh->radio) {
+		/* switch to radio mode */
+		tw68_tvaudio_setinput(dev, &card(dev).radio);
+		tw_call_all(dev, tuner, s_radio);
+	} else {
+		/* switch to video/vbi mode */
+		tw68_tvaudio_setinput(dev, dev->input);
+	}
+	return 0;
+}
+
+static ssize_t
+video_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
+{
+	struct tw68_fh *fh = file->private_data;
+
+	switch (fh->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		if (res_locked(fh->dev, RESOURCE_VIDEO))
+			return -EBUSY;
+		return videobuf_read_one(tw68_queue(fh),
+					 data, count, ppos,
+					 file->f_flags & O_NONBLOCK);
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+		if (!res_get(fh, RESOURCE_VBI))
+			return -EBUSY;
+		return videobuf_read_stream(tw68_queue(fh),
+					    data, count, ppos, 1,
+					    file->f_flags & O_NONBLOCK);
+		break;
+	default:
+		BUG();
+		return 0;
+	}
+}
+
+static unsigned int
+video_poll(struct file *file, struct poll_table_struct *wait)
+{
+	struct tw68_fh *fh = file->private_data;
+	struct videobuf_buffer *buf = NULL;
+
+	if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type)
+		return videobuf_poll_stream(file, &fh->vbi, wait);
+
+	if (res_check(fh, RESOURCE_VIDEO)) {
+		if (!list_empty(&fh->cap.stream))
+			buf = list_entry(fh->cap.stream.next,
+				struct videobuf_buffer, stream);
+	} else {
+		mutex_lock(&fh->cap.vb_lock);
+		if (UNSET == fh->cap.read_off) {
+			/* need to capture a new frame */
+			if (res_locked(fh->dev, RESOURCE_VIDEO))
+				goto err;
+			if (0 != fh->cap.ops->buf_prepare(&fh->cap,
+					fh->cap.read_buf, fh->cap.field))
+				goto err;
+			fh->cap.ops->buf_queue(&fh->cap, fh->cap.read_buf);
+			fh->cap.read_off = 0;
+		}
+		mutex_unlock(&fh->cap.vb_lock);
+		buf = fh->cap.read_buf;
+	}
+
+	if (!buf)
+		return POLLERR;
+
+	poll_wait(file, &buf->done, wait);
+	if (buf->state == VIDEOBUF_DONE ||
+	    buf->state == VIDEOBUF_ERROR)
+		return POLLIN | POLLRDNORM;
+	return 0;
+
+err:
+	mutex_unlock(&fh->cap.vb_lock);
+	return POLLERR;
+}
+
+static int video_release(struct file *file)
+{
+	struct tw68_fh  *fh  = file->private_data;
+	struct tw68_dev *dev = fh->dev;
+
+	/* stop video capture */
+	if (res_check(fh, RESOURCE_VIDEO)) {
+		videobuf_streamoff(&fh->cap);
+		res_free(fh , RESOURCE_VIDEO);
+	}
+	if (fh->cap.read_buf) {
+		buffer_release(&fh->cap, fh->cap.read_buf);
+		kfree(fh->cap.read_buf);
+	}
+
+	/* stop vbi capture */
+	if (res_check(fh, RESOURCE_VBI)) {
+		videobuf_stop(&fh->vbi);
+		res_free(fh, RESOURCE_VBI);
+	}
+
+#if 0
+	tw_call_all(dev, core, s_standby, 0);
+#endif
+
+	/* free stuff */
+	videobuf_mmap_free(&fh->cap);
+	videobuf_mmap_free(&fh->vbi);
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34)
+	v4l2_prio_close(&dev->prio, &fh->prio);
+#else
+	v4l2_prio_close(&dev->prio, fh->prio);
+#endif
+	file->private_data = NULL;
+	kfree(fh);
+	return 0;
+}
+
+static int video_mmap(struct file *file, struct vm_area_struct * vma)
+{
+	struct tw68_fh *fh = file->private_data;
+
+	return videobuf_mmap_mapper(tw68_queue(fh), vma);
+}
+
+/* ------------------------------------------------------------------ */
+
+#if 0
+static int tw68_try_get_set_fmt_vbi_cap(struct file *file, void *priv,
+						struct v4l2_format *f)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	struct tw68_tvnorm *norm = dev->tvnorm;
+
+	f->fmt.vbi.sampling_rate = 6750000 * 4;
+	f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */;
+	f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+	f->fmt.vbi.offset = 64 * 4;
+	f->fmt.vbi.start[0] = norm->vbi_v_start_0;
+	f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 + 1;
+	f->fmt.vbi.start[1] = norm->vbi_v_start_1;
+	f->fmt.vbi.count[1] = f->fmt.vbi.count[0];
+	f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */
+
+#if 0
+	if (V4L2_STD_PAL == norm->id) {
+		/* FIXME */
+		f->fmt.vbi.start[0] += 3;
+		f->fmt.vbi.start[1] += 3*2;
+	}
+#endif
+	return 0;
+}
+#endif
+
+/*
+ * 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_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	f->fmt.pix.width        = fh->width;
+	f->fmt.pix.height       = fh->height;
+	f->fmt.pix.field        = fh->cap.field;
+	f->fmt.pix.pixelformat  = fh->fmt->fourcc;
+	f->fmt.pix.bytesperline =
+		(f->fmt.pix.width * (fh->fmt->depth)) >> 3;
+	f->fmt.pix.sizeimage =
+		f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace	= V4L2_COLORSPACE_SMPTE170M;
+	return 0;
+}
+
+static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
+						struct v4l2_format *f)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	struct tw68_format *fmt;
+	enum v4l2_field field;
+	unsigned int maxw, maxh;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+	if (NULL == fmt)
+		return -EINVAL;
+
+	field = f->fmt.pix.field;
+	maxw  = min(dev->crop_current.width*4,  dev->crop_bounds.width);
+	maxh  = min(dev->crop_current.height*4, dev->crop_bounds.height);
+
+	if (V4L2_FIELD_ANY == field) {
+		field = (f->fmt.pix.height > maxh/2)
+			? V4L2_FIELD_INTERLACED
+			: V4L2_FIELD_BOTTOM;
+	}
+	switch (field) {
+	case V4L2_FIELD_TOP:
+	case V4L2_FIELD_BOTTOM:
+		break;
+	case V4L2_FIELD_INTERLACED:
+		maxh = maxh * 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	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 > maxw)
+		f->fmt.pix.width = maxw;
+	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;
+
+	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_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int err;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	err = tw68_try_fmt_vid_cap(file, priv, f);
+	if (0 != err)
+		return err;
+
+	fh->fmt       = format_by_fourcc(f->fmt.pix.pixelformat);
+	fh->width     = f->fmt.pix.width;
+	fh->height    = f->fmt.pix.height;
+	fh->cap.field = f->fmt.pix.field;
+	/*
+	 * The following lines are to make v4l2-test program happy.
+	 * The docs should be checked to assure they make sense.
+	 */
+	f->fmt.pix.colorspace	= V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.priv = 0;
+	return 0;
+}
+
+static int tw68_queryctrl(struct file *file, void *priv,
+			  struct v4l2_queryctrl *c)
+{
+	const struct v4l2_queryctrl *ctrl;
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if ((c->id <  V4L2_CID_BASE || c->id >= V4L2_CID_LASTP1)
+#if 0
+	     && (c->id <  V4L2_CID_PRIVATE_BASE ||
+	     c->id >= V4L2_CID_PRIVATE_LASTP1)
+#endif
+	)
+		return -EINVAL;
+	ctrl = ctrl_by_id(c->id);
+	if (NULL == ctrl)
+		return -EINVAL;
+	*c = *ctrl;
+	return 0;
+}
+
+static int tw68_enum_input(struct file *file, void *priv,
+					struct v4l2_input *i)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	unsigned int n;
+
+	n = i->index;
+	dprintk(DBG_FLOW, "%s: index is %d\n", __func__, n);
+	if (n >= TW68_INPUT_MAX) {
+		dprintk(DBG_FLOW, "%s: INPUT_MAX reached\n", __func__);
+		return -EINVAL;
+	}
+	if (NULL == card_in(dev, n).name) {
+		dprintk(DBG_FLOW, "%s: End of list\n", __func__);
+		return -EINVAL;
+	}
+	memset(i, 0, sizeof(*i));
+	i->index = n;
+	i->type  = V4L2_INPUT_TYPE_CAMERA;
+	strcpy(i->name, card_in(dev, n).name);
+	if (card_in(dev, n).tv)
+		i->type = V4L2_INPUT_TYPE_TUNER;
+	i->audioset = 1;
+	/* If the query is for the current input, get live data */
+	if (n == dev->hw_input->vmux) {
+		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 = TW68_NORMS;
+	return 0;
+}
+
+static int tw68_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	*i = dev->input->vmux;
+	return 0;
+}
+
+static int tw68_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int err;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34)
+		err = v4l2_prio_check(&dev->prio, &fh->prio);
+#else
+		err = v4l2_prio_check(&dev->prio, fh->prio);
+#endif
+		if (0 != err)
+	if (0 != err)
+		return err;
+
+	if (i < 0  ||  i >= TW68_INPUT_MAX)
+		return -EINVAL;
+	if (NULL == card_in(dev, i).name)
+		return -EINVAL;
+	mutex_lock(&dev->lock);
+	video_mux(dev, i);
+	mutex_unlock(&dev->lock);
+	return 0;
+}
+
+static int tw68_querycap(struct file *file, void  *priv,
+					struct v4l2_capability *cap)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	unsigned int tuner_type = dev->tuner_type;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	strcpy(cap->driver, "tw68");
+	strlcpy(cap->card, tw68_boards[dev->board].name,
+		sizeof(cap->card));
+	sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
+	cap->version = TW68_VERSION_CODE;
+	cap->capabilities =
+		V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_VBI_CAPTURE |
+		V4L2_CAP_READWRITE |
+		V4L2_CAP_STREAMING |
+		V4L2_CAP_TUNER;
+
+	if ((tuner_type == TUNER_ABSENT) || (tuner_type == UNSET))
+		cap->capabilities &= ~V4L2_CAP_TUNER;
+	return 0;
+}
+
+static int tw68_s_std_internal(struct tw68_dev *dev, struct tw68_fh *fh,
+			v4l2_std_id *id)
+{
+/*	unsigned long flags; */
+	unsigned int i;
+	v4l2_std_id fixup;
+	int err;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (fh) {
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34)
+		err = v4l2_prio_check(&dev->prio, &fh->prio);
+#else
+		err = v4l2_prio_check(&dev->prio, fh->prio);
+#endif
+		if (0 != err)
+		if (0 != err)
+			return err;
+	}
+
+	/* 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;
+
+	/* TODO - verify this additional work with SECAM applies to TW */
+	if ((*id & V4L2_STD_SECAM) && (secam[0] != '-')) {
+		if (secam[0] == 'L' || secam[0] == 'l') {
+			if (secam[1] == 'C' || secam[1] == 'c')
+				fixup = V4L2_STD_SECAM_LC;
+			else
+				fixup = V4L2_STD_SECAM_L;
+		} else {
+			if (secam[0] == 'D' || secam[0] == 'd')
+				fixup = V4L2_STD_SECAM_DK;
+			else
+				fixup = V4L2_STD_SECAM;
+		}
+		for (i = 0; i < TVNORMS; i++)
+			if (fixup == tvnorms[i].id)
+				break;
+	}
+
+	*id = tvnorms[i].id;
+	mutex_lock(&dev->lock);
+	set_tvnorm(dev, &tvnorms[i]);	/* do the actual setting */
+	tw68_tvaudio_do_scan(dev);
+	mutex_unlock(&dev->lock);
+	return 0;
+}
+
+static int tw68_s_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	return tw68_s_std_internal(fh->dev, fh, id);
+}
+
+static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	*id = dev->tvnorm->id;
+	return 0;
+}
+
+static int tw68_g_tuner(struct file *file, void *priv,
+					struct v4l2_tuner *t)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int n;
+
+	if (unlikely(UNSET == dev->tuner_type))
+		return -EINVAL;
+	if (0 != t->index)
+		return -EINVAL;
+	memset(t, 0, sizeof(*t));
+	for (n = 0; n < TW68_INPUT_MAX; n++)
+		if (card_in(dev, n).tv)
+			break;
+	if (n == TW68_INPUT_MAX)
+		return -EINVAL;
+#if 0
+	if (NULL != card_in(dev, n).name) {
+		strcpy(t->name, "Television");
+		t->type = V4L2_TUNER_ANALOG_TV;
+		t->capability = V4L2_TUNER_CAP_NORM |
+			V4L2_TUNER_CAP_STEREO |
+			V4L2_TUNER_CAP_LANG1 |
+			V4L2_TUNER_CAP_LANG2;
+		t->rangehigh = 0xffffffffUL;
+		t->rxsubchans = tw68_tvaudio_getstereo(dev);
+		t->audmode = tw68_tvaudio_rx2mode(t->rxsubchans);
+	}
+	if (0 != (saa_readb(TW68_STATUS_VIDEO1) & 0x03))
+		t->signal = 0xffff;
+#endif
+	return 0;
+}
+
+static int tw68_s_tuner(struct file *file, void *priv,
+					struct v4l2_tuner *t)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int err;
+#if 0
+	int rx, mode
+#endif
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34)
+		err = v4l2_prio_check(&dev->prio, &fh->prio);
+#else
+		err = v4l2_prio_check(&dev->prio, fh->prio);
+#endif
+		if (0 != err)
+	if (0 != err)
+		return err;
+
+#if 0
+	mode = dev->thread.mode;
+	if (UNSET == mode) {
+		rx   = tw68_tvaudio_getstereo(dev);
+		mode = tw68_tvaudio_rx2mode(t->rxsubchans);
+	}
+	if (mode != t->audmode)
+		dev->thread.mode = t->audmode;
+#endif
+	return 0;
+}
+
+static int tw68_g_frequency(struct file *file, void *priv,
+					struct v4l2_frequency *f)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	if (unlikely(dev->tuner_type))
+		return -EINVAL;
+	f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+/*	f->frequency = dev->ctl_freq; */
+
+	return 0;
+}
+
+static int tw68_s_frequency(struct file *file, void *priv,
+					struct v4l2_frequency *f)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int err;
+
+	if (unlikely(UNSET == dev->tuner_type))
+		return -EINVAL;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34)
+		err = v4l2_prio_check(&dev->prio, &fh->prio);
+#else
+		err = v4l2_prio_check(&dev->prio, fh->prio);
+#endif
+		if (0 != err)
+	if (0 != err)
+		return err;
+
+	if (0 != f->tuner)
+		return -EINVAL;
+	if (0 == fh->radio && V4L2_TUNER_ANALOG_TV != f->type)
+		return -EINVAL;
+	if (1 == fh->radio && V4L2_TUNER_RADIO != f->type)
+		return -EINVAL;
+	mutex_lock(&dev->lock);
+/*	dev->ctl_freq = f->frequency; */
+
+	tw_call_all(dev, tuner, s_frequency, f);
+
+	tw68_tvaudio_do_scan(dev);
+	mutex_unlock(&dev->lock);
+	return 0;
+}
+
+static int tw68_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+	strcpy(a->name, "audio");
+	return 0;
+}
+
+static int tw68_s_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+	return 0;
+}
+
+static int tw68_g_priority(struct file *file, void *f, enum v4l2_priority *p)
+{
+	struct tw68_fh *fh = f;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	*p = v4l2_prio_max(&dev->prio);
+	return 0;
+}
+
+static int tw68_s_priority(struct file *file, void *f,
+					enum v4l2_priority prio)
+{
+	struct tw68_fh *fh = f;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	return v4l2_prio_change(&dev->prio, &fh->prio, prio);
+}
+
+static int tw68_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *f)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (f->index >= FORMATS)
+		return -EINVAL;
+
+	strlcpy(f->description, formats[f->index].name,
+		sizeof(f->description));
+
+	f->pixelformat = formats[f->index].fourcc;
+
+	return 0;
+}
+
+static int tw68_cropcap(struct file *file, void *priv,
+					struct v4l2_cropcap *cap)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	cap->bounds  = dev->crop_bounds;
+	cap->defrect = dev->crop_defrect;
+	cap->pixelaspect.numerator   = 1;
+	cap->pixelaspect.denominator = 1;
+	if (dev->tvnorm->id & V4L2_STD_525_60) {
+		cap->pixelaspect.numerator   = 11;
+		cap->pixelaspect.denominator = 10;
+	}
+	if (dev->tvnorm->id & V4L2_STD_625_50) {
+		cap->pixelaspect.numerator   = 54;
+		cap->pixelaspect.denominator = 59;
+	}
+	return 0;
+}
+
+static int tw68_g_crop(struct file *file, void *f, struct v4l2_crop *crop)
+{
+	struct tw68_fh *fh = f;
+	struct tw68_dev *dev = fh->dev;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	crop->c = dev->crop_current;
+	return 0;
+}
+
+static int tw68_s_crop(struct file *file, void *f, struct v4l2_crop *crop)
+{
+	struct tw68_fh *fh = f;
+	struct tw68_dev *dev = fh->dev;
+	struct v4l2_rect *b = &dev->crop_bounds;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (res_locked(fh->dev, RESOURCE_VIDEO))
+		return -EBUSY;
+
+	if ((crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) ||
+	    (crop->c.height < 0) || (crop->c.width < 0)) {
+		dprintk(DBG_UNEXPECTED, "%s: invalid request\n", __func__);
+		return -EINVAL;
+	}
+
+	if (crop->c.top < b->top)
+		crop->c.top = b->top;
+	if (crop->c.top > b->top + b->height)
+		crop->c.top = b->top + b->height;
+	if (crop->c.height > b->top - crop->c.top + b->height)
+		crop->c.height = b->top - crop->c.top + b->height;
+
+	if (crop->c.left < b->left)
+		crop->c.left = b->left;
+	if (crop->c.left > b->left + b->width)
+		crop->c.left = b->left + b->width;
+	if (crop->c.width > b->left - crop->c.left + b->width)
+		crop->c.width = b->left - crop->c.left + b->width;
+
+	dprintk(DBG_FLOW, "%s: setting cropping rectangle: top=%d, left=%d, "
+		    "width=%d, height=%d\n", __func__, crop->c.top,
+		    crop->c.left, crop->c.width, crop->c.height);
+	dev->crop_current = crop->c;
+	return 0;
+}
+
+/*
+ * Wrappers for the v4l2_ioctl_ops functions
+ */
+#ifdef CONFIG_VIDEO_V4L1_COMPAT
+static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
+{
+	struct tw68_fh *fh = file->private_data;
+	return videobuf_cgmbuf(tw68_queue(fh), mbuf, 8);
+}
+#endif
+
+static int tw68_reqbufs(struct file *file, void *priv,
+					struct v4l2_requestbuffers *p)
+{
+	struct tw68_fh *fh = priv;
+	return videobuf_reqbufs(tw68_queue(fh), p);
+}
+
+static int tw68_querybuf(struct file *file, void *priv,
+					struct v4l2_buffer *b)
+{
+	struct tw68_fh *fh = priv;
+	return videobuf_querybuf(tw68_queue(fh), b);
+}
+
+static int tw68_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+	struct tw68_fh *fh = priv;
+	return videobuf_qbuf(tw68_queue(fh), b);
+}
+
+static int tw68_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+	struct tw68_fh *fh = priv;
+	return videobuf_dqbuf(tw68_queue(fh), b,
+				file->f_flags & O_NONBLOCK);
+}
+
+static int tw68_streamon(struct file *file, void *priv,
+					enum v4l2_buf_type type)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int res = tw68_resource(fh);
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (!res_get(fh, res))
+		return -EBUSY;
+
+	tw68_buffer_requeue(dev, &dev->video_q);
+	return videobuf_streamon(tw68_queue(fh));
+}
+
+static int tw68_streamoff(struct file *file, void *priv,
+					enum v4l2_buf_type type)
+{
+	int err;
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+	int res = tw68_resource(fh);
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	err = videobuf_streamoff(tw68_queue(fh));
+	if (err < 0)
+		return err;
+	res_free(fh, res);
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+/*
+ * 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;
+
+	printk(KERN_DEBUG "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;
+		printk(KERN_DEBUG "%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;
+		printk(KERN_DEBUG "%s", line);
+	}
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;
+
+	tw68_dump_regs(dev);
+	return 0;
+}
+
+static int vidioc_g_register(struct file *file, void *priv,
+			      struct v4l2_dbg_register *reg)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;	/* needed for tw_readb */
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	if (!v4l2_chip_match_host(&reg->match))
+		dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__);
+		return -EINVAL;
+	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,
+				struct v4l2_dbg_register *reg)
+{
+	struct tw68_fh *fh = priv;
+	struct tw68_dev *dev = fh->dev;	/* needed for tw_writeb */
+
+	dprintk(DBG_FLOW, "%s: request to set reg 0x%04x to 0x%02x\n",
+		__func__, (unsigned int)reg->reg, (unsigned int)reg->val);
+	if (!v4l2_chip_match_host(&reg->match)) {
+		dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__);
+		return -EINVAL;
+	}
+	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_file_operations video_fops = {
+	.owner			= THIS_MODULE,
+	.open			= video_open,
+	.release		= video_release,
+	.read			= video_read,
+	.poll			= video_poll,
+	.mmap			= video_mmap,
+	.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			= tw68_reqbufs,
+	.vidioc_querybuf		= tw68_querybuf,
+	.vidioc_qbuf			= tw68_qbuf,
+	.vidioc_dqbuf			= tw68_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_queryctrl		= tw68_queryctrl,
+	.vidioc_g_ctrl			= tw68_g_ctrl,
+	.vidioc_s_ctrl			= tw68_s_ctrl,
+	.vidioc_streamon		= tw68_streamon,
+	.vidioc_streamoff		= tw68_streamoff,
+	.vidioc_g_priority		= tw68_g_priority,
+	.vidioc_s_priority		= tw68_s_priority,
+	.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_cropcap			= tw68_cropcap,
+	.vidioc_g_crop			= tw68_g_crop,
+	.vidioc_s_crop			= tw68_s_crop,
+/*
+ * Functions not yet implemented / not yet passing tests.
+ */
+
+#if 0
+	.vidioc_g_fmt_vbi_cap		= tw68_try_get_set_fmt_vbi_cap,
+	.vidioc_try_fmt_vbi_cap		= tw68_try_get_set_fmt_vbi_cap,
+	.vidioc_s_fmt_vbi_cap		= tw68_try_get_set_fmt_vbi_cap,
+#endif
+	.vidioc_g_audio			= tw68_g_audio,
+	.vidioc_s_audio			= tw68_s_audio,
+	.vidioc_g_tuner			= tw68_g_tuner,
+	.vidioc_s_tuner			= tw68_s_tuner,
+	.vidioc_g_frequency		= tw68_g_frequency,
+	.vidioc_s_frequency		= tw68_s_frequency,
+#ifdef CONFIG_VIDEO_V4L1_COMPAT
+	.vidiocgmbuf			= vidiocgmbuf,
+#endif
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_log_status		= vidioc_log_status,
+	.vidioc_g_register              = vidioc_g_register,
+	.vidioc_s_register              = vidioc_s_register,
+#endif
+};
+
+/* ------------------------------------------------------------------ */
+/* exported stuff                                                     */
+struct video_device tw68_video_template = {
+	.name			= "tw68_video",
+	.fops			= &video_fops,
+	.ioctl_ops		= &video_ioctl_ops,
+	.minor			= -1,
+	.tvnorms		= TW68_NORMS,
+	.current_norm		= V4L2_STD_PAL,
+};
+
+struct video_device tw68_radio_template = {
+	.name			= "tw68_radio",
+};
+
+int tw68_videoport_init(struct tw68_dev *dev)
+{
+	return 0;
+}
+
+void tw68_set_tvnorm_hw(struct tw68_dev *dev)
+{
+	tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format);
+	return;
+}
+
+int tw68_video_init1(struct tw68_dev *dev)
+{
+	int i;
+
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	/* sanitycheck insmod options */
+	if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME)
+		gbuffers = 2;
+	if (gbufsz < 0 || gbufsz > gbufsz_max)
+		gbufsz = gbufsz_max;
+	gbufsz = (gbufsz + PAGE_SIZE - 1) & PAGE_MASK;
+
+	/* put some sensible defaults into the data structures ... */
+	for (i = 0; i < CTRLS; i++)
+		tw68_s_ctrl_value(dev, video_ctrls[i].id,
+				  video_ctrls[i].default_value);
+#if 0
+	if (dev->tda9887_conf && dev->ctl_automute)
+		dev->tda9887_conf |= TDA9887_AUTOMUTE;
+	dev->automute       = 0;
+#endif
+	INIT_LIST_HEAD(&dev->video_q.queued);
+	INIT_LIST_HEAD(&dev->video_q.active);
+	init_timer(&dev->video_q.timeout);
+	dev->video_q.timeout.function	= tw68_buffer_timeout;
+	dev->video_q.timeout.data	= (unsigned long)(&dev->video_q);
+	dev->video_q.dev		= dev;
+	dev->video_q.buf_compat		= tw68_check_video_fmt;
+	dev->video_q.start_dma		= tw68_video_start_dma;
+	tw68_risc_stopper(dev->pci, &dev->video_q.stopper);
+
+	if (tw68_boards[dev->board].video_out)
+		tw68_videoport_init(dev);
+
+	return 0;
+}
+
+int tw68_video_init2(struct tw68_dev *dev)
+{
+	dprintk(DBG_FLOW, "%s\n", __func__);
+	set_tvnorm(dev, &tvnorms[0]);
+	video_mux(dev, 0);
+/*
+	tw68_tvaudio_setmut(dev);
+	tw68_tvaudio_setvolume(dev, dev->ctl_volume);
+*/
+	return 0;
+}
+
+/*
+ * tw68_irq_video_signalchange
+ *
+ * TODO:
+ * Check for presence of video signal.  If not present, mute audio.
+ * If present, log type of signal present.
+ */
+void tw68_irq_video_signalchange(struct tw68_dev *dev)
+{
+	return;
+}
+
+/*
+ * 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_dmaqueue *q = &dev->video_q;
+		dprintk(DBG_FLOW | DBG_TESTING, "DMAPI interrupt\n");
+		spin_lock(&dev->slock);
+		/*
+		 * tw68_wakeup will take care of the buffer handling,
+		 * plus any non-video requirements.
+		 */
+		tw68_wakeup(q, &dev->video_fieldcount);
+		spin_unlock(&dev->slock);
+		/* Check whether we have gotten into 'stopper' code */
+		reg = tw_readl(TW68_DMAP_PP);
+		if ((reg >= q->stopper.dma) &&
+		    (reg < q->stopper.dma + q->stopper.size)) {
+			/* Yes - log the information */
+			dprintk(DBG_FLOW | DBG_TESTING,
+				"%s: stopper risc code entered\n", __func__);
+		}
+		status &= ~(TW68_DMAPI);
+		if (0 == status)
+			return;
+	}
+	if (status & (TW68_VLOCK | TW68_HLOCK)) { /* lost sync */
+		dprintk(DBG_UNUSUAL, "Lost sync\n");
+	}
+	if (status & TW68_PABORT) {	/* TODO - what should we do? */
+		dprintk(DBG_UNEXPECTED, "PABORT interrupt\n");
+	}
+	if (status & TW68_DMAPERR) {
+		dprintk(DBG_UNEXPECTED, "DMAPERR interrupt\n");
+#if 0
+		/* Stop risc & fifo */
+		tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+		tw_clearl(TW68_INTMASK, dev->board_virqmask);
+		dev->pci_irqmask &= ~dev->board_virqmask;
+#endif
+	}
+	/*
+	 * On TW6800, FDMIS is apparently generated if video input is switched
+	 * during operation.  Therefore, it is not enabled for that chip.
+	 */
+	if (status & TW68_FDMIS) {	/* logic error somewhere */
+		dprintk(DBG_UNEXPECTED, "FDMIS interrupt\n");
+		/* Stop risc & fifo */
+//		tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
+//		tw_clearl(TW68_INTMASK, dev->board_virqmask);
+//		dev->pci_irqmask &= ~dev->board_virqmask;
+	}
+	if (status & TW68_FFOF) {	/* probably a logic error */
+		reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN;
+		tw_clearl(TW68_DMAC, TW68_FIFO_EN);
+		dprintk(DBG_UNUSUAL, "FFOF interrupt\n");
+		tw_setl(TW68_DMAC, reg);
+	}
+	if (status & TW68_FFERR)
+		dprintk(DBG_UNEXPECTED, "FFERR interrupt\n");
+	return;
+}

+ 588 - 0
drivers/media/pci/tw68/tw68.h

@@ -0,0 +1,588 @@
+/*
+ *  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 <wbrack@mmm.com.hk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ *  02111-1307  USA
+ */
+
+#include <linux/version.h>
+#define	TW68_VERSION_CODE	KERNEL_VERSION(0, 0, 8)
+
+#include <linux/pci.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/videodev2.h>
+#include <linux/kdev_t.h>
+#include <linux/input.h>
+#include <linux/notifier.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+
+#include <asm/io.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+
+#include <media/tuner.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
+#  include <media/ir-common.h>
+#endif
+#include <media/ir-kbd-i2c.h>
+#include <media/videobuf-dma-sg.h>
+
+#include "btcx-risc.h"
+#include "tw68-reg.h"
+
+#define	UNSET	(-1U)
+
+/*
+ * dprintk statement within the code use a 'level' argument.  For
+ * our purposes, we use the following levels:
+ */
+#define	DBG_UNEXPECTED		(1 << 0)
+#define	DBG_UNUSUAL		(1 << 1)
+#define	DBG_TESTING		(1 << 2)
+#define	DBG_BUFF		(1 << 3)
+#define	DBG_FLOW		(1 << 15)
+
+/* 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
+
+/* subsystem vendor ID's */
+#define	TW68_PCI_ID_TECHWELL	0x1797
+
+#define TW68_NORMS (\
+	V4L2_STD_NTSC   | V4L2_STD_PAL       | V4L2_STD_SECAM    | \
+	V4L2_STD_PAL_BG | V4L2_STD_PAL_DK    | V4L2_STD_PAL_I    | \
+	V4L2_STD_PAL_M  | V4L2_STD_PAL_Nc    | V4L2_STD_PAL_60   | \
+	V4L2_STD_525_60 | V4L2_STD_625_50    | \
+	V4L2_STD_SECAM_L| V4L2_STD_SECAM_LC  | V4L2_STD_SECAM_DK)
+
+#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)
+
+typedef enum {
+	TW6800,
+	TW6801,
+	TW6804,
+	TWXXXX,
+} TW68_DECODER_TYPE;
+/* ----------------------------------------------------------- */
+/* 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			8
+
+/* ----------------------------------------------------------- */
+/* enums						       */
+
+enum tw68_mpeg_type {
+	TW68_MPEG_UNUSED,
+	TW68_MPEG_EMPRESS,
+	TW68_MPEG_DVB,
+};
+
+enum tw68_audio_in {
+	TV      = 1,
+	LINE1   = 2,
+	LINE2   = 3,
+	LINE2_LEFT,
+};
+
+enum tw68_video_out {
+	CCIR656 = 1,
+};
+
+/* Structs for card definition */
+struct tw68_input {
+	char			*name;		/* text description */
+	unsigned int		vmux;		/* mux value */
+	enum tw68_audio_in	mux;
+	unsigned int		gpio;
+	unsigned int		tv:1;
+};
+
+struct tw68_board {
+	char			*name;
+	unsigned int		audio_clock;
+
+	/* input switching */
+	unsigned int		gpiomask;
+	struct tw68_input	inputs[TW68_INPUT_MAX];
+	struct tw68_input	radio;
+	struct tw68_input	mute;
+
+	/* i2c chip info */
+	unsigned int		tuner_type;
+	unsigned int		radio_type;
+	unsigned char		tuner_addr;
+	unsigned char		radio_addr;
+
+	unsigned int		tda9887_conf;
+	unsigned int		tuner_config;
+
+	enum tw68_video_out	video_out;
+	enum tw68_mpeg_type	mpeg;
+	unsigned int		vid_port_opts;
+};
+
+#define card_has_radio(dev)	(NULL != tw68_boards[dev->board].radio.name)
+#define card_has_mpeg(dev)	(TW68_MPEG_UNUSED != \
+					tw68_boards[dev->board].mpeg)
+#define card_in(dev, n)		(tw68_boards[dev->board].inputs[n])
+#define card(dev)		(tw68_boards[dev->board])
+
+/* ----------------------------------------------------------- */
+/* device / file handle status                                 */
+
+#define	RESOURCE_VIDEO			1
+#define	RESOURCE_VBI			2
+
+#define	INTERLACE_AUTO			0
+#define	INTERLACE_ON			1
+#define	INTERLACE_OFF			2
+
+#define	BUFFER_TIMEOUT	msecs_to_jiffies(500)	/* 0.5 seconds */
+
+struct tw68_dev;	/* forward delclaration */
+
+/* tvaudio thread status */
+struct tw68_thread {
+	struct task_struct	*thread;
+	unsigned int		scan1;
+	unsigned int		scan2;
+	unsigned int		mode;
+	unsigned int		stopped;
+};
+
+/* buffer for one video/vbi/ts frame */
+struct tw68_buf {
+	/* common v4l buffer stuff -- must be first */
+	struct videobuf_buffer vb;
+
+	/* tw68 specific */
+	struct tw68_format	*fmt;
+	struct tw68_input	*input;
+	unsigned int		top_seen;
+	int (*activate)(struct tw68_dev *dev,
+			struct tw68_buf *buf,
+			struct tw68_buf *next);
+	struct btcx_riscmem	risc;
+	unsigned int		bpl;
+};
+
+struct tw68_dmaqueue {
+	struct tw68_dev		*dev;
+	struct list_head	active;
+	struct list_head	queued;
+	struct timer_list	timeout;
+	struct btcx_riscmem	stopper;
+	int (*buf_compat)(struct tw68_buf *prev,
+			  struct tw68_buf *buf);
+	int (*start_dma)(struct tw68_dev *dev,
+			 struct tw68_dmaqueue *q,
+			 struct tw68_buf *buf);
+};
+
+/* video filehandle status */
+struct tw68_fh {
+	struct tw68_dev		*dev;
+	unsigned int		radio;
+	enum v4l2_buf_type	type;
+	unsigned int		resources;
+	enum v4l2_priority	prio;
+
+	/* video capture */
+	struct tw68_format	*fmt;
+	unsigned int		width, height;
+	struct videobuf_queue	cap;	/* also used for overlay */
+
+	/* vbi capture */
+	struct videobuf_queue	vbi;
+};
+
+/* dmasound dsp status */
+struct tw68_dmasound {
+	struct mutex		lock;
+	int			minor_mixer;
+	int			minor_dsp;
+	unsigned int		users_dsp;
+
+	/* mixer */
+	enum tw68_audio_in	input;
+	unsigned int		count;
+	unsigned int		line1;
+	unsigned int		line2;
+
+	/* dsp */
+	unsigned int		afmt;
+	unsigned int		rate;
+	unsigned int		channels;
+	unsigned int		recording_on;
+	unsigned int		dma_running;
+	unsigned int		blocks;
+	unsigned int		blksize;
+	unsigned int		bufsize;
+	struct videobuf_dmabuf	dma;
+	unsigned int		dma_blk;
+	unsigned int		read_offset;
+	unsigned int		read_count;
+	void 			*priv_data;
+	struct snd_pcm_substream	*substream;
+};
+
+struct tw68_fmt {
+	char			*name;
+	u32			fourcc;	/* v4l2 format id */
+	int			depth;
+	int			flags;
+	u32			twformat;
+};
+
+/* ts/mpeg status */
+struct tw68_ts {
+	/* TS capture */
+	int			nr_packets;
+	int			nr_bufs;
+};
+
+/* ts/mpeg ops */
+struct tw68_mpeg_ops {
+	enum tw68_mpeg_type	type;
+	struct list_head	next;
+	int			(*init)(struct tw68_dev *dev);
+	int			(*fini)(struct tw68_dev *dev);
+	void			(*signal_change)(struct tw68_dev *dev);
+};
+
+enum tw68_ts_status {
+	TW68_TS_STOPPED,
+	TW68_TS_BUFF_DONE,
+	TW68_TS_STARTED,
+};
+
+/* global device status */
+struct tw68_dev {
+	struct list_head	devlist;
+	struct mutex		lock;
+	spinlock_t		slock;
+	struct v4l2_prio_state	prio;
+	struct v4l2_device	v4l2_dev;
+	/* workstruct for loading modules */
+	struct work_struct request_module_wk;
+
+	/* insmod option/autodetected */
+	int			autodetected;
+
+	/* various device info */
+	TW68_DECODER_TYPE	vdecoder;
+	unsigned int		resources;
+	struct video_device	*video_dev;
+	struct video_device	*radio_dev;
+	struct video_device	*vbi_dev;
+	struct tw68_dmasound	dmasound;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
+	/* infrared remote */
+	int			has_remote;
+	struct card_ir		*remote;
+#endif
+
+	/* pci i/o */
+	char			name[32];
+	int			nr;
+	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;
+
+	/* config info */
+	unsigned int		board;
+	unsigned int		tuner_type;
+	unsigned int 		radio_type;
+	unsigned char		tuner_addr;
+	unsigned char		radio_addr;
+
+	unsigned int		tda9887_conf;
+	unsigned int		gpio_value;
+
+	/* i2c i/o */
+	struct i2c_algo_bit_data i2c_algo;
+	struct i2c_adapter	i2c_adap;
+	struct i2c_client	i2c_client;
+	u32			i2c_state;
+	u32			i2c_done;
+	wait_queue_head_t	i2c_queue;
+	int			i2c_rc;
+	unsigned char		eedata[256];
+
+	/* video+ts+vbi capture */
+	struct tw68_dmaqueue	video_q;
+	struct tw68_dmaqueue	vbi_q;
+	unsigned int		video_fieldcount;
+	unsigned int		vbi_fieldcount;
+
+	/* various v4l controls */
+	struct tw68_tvnorm	*tvnorm;	/* video */
+	struct tw68_tvaudio	*tvaudio;
+#if 0
+	unsigned int		ctl_input;
+	int			ctl_bright;
+	int			ctl_contrast;
+	int			ctl_hue;
+	int			ctl_saturation;
+	int			ctl_freq;
+	int			ctl_mute;	/* audio */
+	int			ctl_volume;
+	int			ctl_invert;	/* private */
+	int			ctl_mirror;
+	int			ctl_y_odd;
+	int			ctl_y_even;
+	int			ctl_automute;
+#endif
+
+	/* crop */
+	struct v4l2_rect	crop_bounds;
+	struct v4l2_rect	crop_defrect;
+	struct v4l2_rect	crop_current;
+
+	/* other global state info */
+	unsigned int		automute;
+	struct tw68_thread	thread;
+	/* input is latest requested by app, hw_input is current hw setting */
+	struct tw68_input	*input;
+	struct tw68_input	*hw_input;
+	unsigned int		hw_mute;
+	int			last_carrier;
+	int			nosignal;
+	unsigned int		insuspend;
+
+	/* TW68_MPEG_* */
+	struct tw68_ts		ts;
+	struct tw68_dmaqueue	ts_q;
+	enum tw68_ts_status 	ts_state;
+	unsigned int 		buff_cnt;
+	struct tw68_mpeg_ops	*mops;
+
+	void (*gate_ctrl)(struct tw68_dev *dev, int open);
+};
+
+/* ----------------------------------------------------------- */
+
+#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_call_all(dev, o, f, args...) do {				\
+	if (dev->gate_ctrl)						\
+		dev->gate_ctrl(dev, 1);					\
+	v4l2_device_call_all(&(dev)->v4l2_dev, 0, o, f , ##args);	\
+	if (dev->gate_ctrl)						\
+		dev->gate_ctrl(dev, 0);					\
+} while (0)
+
+#define tw_wait(us) { udelay(us); }
+
+static inline struct tw68_dev *to_tw68_dev(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct tw68_dev, v4l2_dev);
+}
+
+/* ----------------------------------------------------------- */
+/* tw68-core.c                                                */
+
+extern struct list_head  tw68_devlist;
+extern struct mutex tw68_devlist_lock;
+extern unsigned int irq_debug;
+
+int tw68_buffer_count(unsigned int size, unsigned int count);
+void tw68_buffer_queue(struct tw68_dev *dev, struct tw68_dmaqueue *q,
+		      struct tw68_buf *buf);
+void tw68_buffer_timeout(unsigned long data);
+int tw68_set_dmabits(struct tw68_dev *dev);
+void tw68_dma_free(struct videobuf_queue *q, struct tw68_buf *buf);
+void tw68_wakeup(struct tw68_dmaqueue *q, unsigned int *field_count);
+int tw68_buffer_requeue(struct tw68_dev *dev, struct tw68_dmaqueue *q);
+
+/* ----------------------------------------------------------- */
+/* tw68-cards.c                                                */
+
+extern struct tw68_board tw68_boards[];
+extern const unsigned int tw68_bcount;
+extern struct pci_device_id __devinitdata tw68_pci_tbl[];
+
+int tw68_board_init1(struct tw68_dev *dev);
+int tw68_board_init2(struct tw68_dev *dev);
+int tw68_tuner_callback(void *priv, int component, int command, int arg);
+
+/* ----------------------------------------------------------- */
+/* tw68-i2c.c                                                  */
+
+int tw68_i2c_register(struct tw68_dev *dev);
+int tw68_i2c_unregister(struct tw68_dev *dev);
+void tw68_irq_i2c(struct tw68_dev *dev, int status);
+
+/* ----------------------------------------------------------- */
+/* tw68-video.c                                                */
+
+extern unsigned int video_debug;
+extern struct video_device tw68_video_template;
+extern struct video_device tw68_radio_template;
+
+int tw68_videoport_init(struct tw68_dev *dev);
+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);
+void tw68_irq_video_signalchange(struct tw68_dev *dev);
+void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status);
+
+/* ----------------------------------------------------------- */
+/* tw68-ts.c                                                   */
+
+int tw68_ts_init1(struct tw68_dev *dev);
+int tw68_ts_fini(struct tw68_dev *dev);
+void tw68_irq_ts_done(struct tw68_dev *dev, unsigned long status);
+
+int tw68_ts_register(struct tw68_mpeg_ops *ops);
+void tw68_ts_unregister(struct tw68_mpeg_ops *ops);
+
+int tw68_ts_init_hw(struct tw68_dev *dev);
+
+/* ----------------------------------------------------------- */
+/* tw68-vbi.c                                                  */
+
+extern struct videobuf_queue_ops tw68_vbi_qops;
+extern struct video_device tw68_vbi_template;
+
+int tw68_vbi_init1(struct tw68_dev *dev);
+int tw68_vbi_fini(struct tw68_dev *dev);
+void tw68_irq_vbi_done(struct tw68_dev *dev, unsigned long status);
+
+/* ----------------------------------------------------------- */
+/* tw68-tvaudio.c                                              */
+
+int tw68_tvaudio_rx2mode(u32 rx);
+
+void tw68_tvaudio_setmute(struct tw68_dev *dev);
+void tw68_tvaudio_setinput(struct tw68_dev *dev,
+			      struct tw68_input *in);
+void tw68_tvaudio_setvolume(struct tw68_dev *dev, int level);
+int tw68_tvaudio_getstereo(struct tw68_dev *dev);
+void tw68_tvaudio_init(struct tw68_dev *dev);
+int tw68_tvaudio_init2(struct tw68_dev *dev);
+int tw68_tvaudio_fini(struct tw68_dev *dev);
+int tw68_tvaudio_do_scan(struct tw68_dev *dev);
+int tw_dsp_writel(struct tw68_dev *dev, int reg, u32 value);
+void tw68_enable_i2s(struct tw68_dev *dev);
+
+/* ----------------------------------------------------------- */
+/* tw68-risc.c                                                 */
+
+int tw68_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc,
+	struct scatterlist *sglist, unsigned int top_offset,
+	unsigned int bottom_offset, unsigned int bpl,
+	unsigned int padding, unsigned int lines);
+int tw68_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc);
+int tw68_risc_overlay(struct tw68_fh *fh, struct btcx_riscmem *risc,
+		      int field_type);